Commit e531e5b0 authored by S Anand's avatar S Anand

ENH: $.template allows template tag. Improve docs

parent 9ba67cc3
# g1
`g1` is library of interaction patterns in [Gramex](https://learn.gramener.com/guide/g1/).
`g1` is a micro-framework collection that makes it easy to write front-end data
applications. It's a humble alternative to [Vue.js](https://vuejs.org/),
[AngularJS](https://angularjs.org/) and [React](https://reactjs.org/).
Install using:
```bash
yarn install g1
# ... OR ...
npm install --save g1
```
To use all features, add this to your HTML:
To use it, include:
```html
<script src="node_modules/g1/dist/g1.min.js"></script>
<script src="node_modules/g1/dist/mapviewer.min.js"></script>
<script src="https://cdn.jsdelivr.net/combine/npm/jquery,npm/lodash,npm/g1"></script>
```
## Interactions
- [$.urlfilter](docs/urlfilter.md) changes URL query parameters when clicked. Used to filter data.
- [$.urlchange](docs/urlchange.md) listens to hash changes and routes events
- [$.search](docs/search.md) highlights elements as you type in a search box
- [$.highlight](docs/highlight.md) toggles classes on elements when clicked or hover
## Components
- [$.formhandler](docs/formhandler.md) renders a HTML table from a [FormHandler URL](https://learn.gramener.com/guide/formhandler/)
- [$.template](docs/template.md) renders lodash templates. Requires [lodash](https://lodash.com/)
- [$.dropdown](docs/dropdown.md) renders a Bootstrap dropdown. Requires [Bootstrap](https://getbootstrap.com/docs/4.2/)
- [$.translate](docs/translate.md) translates text to other languages using [Gramex translate](https://learn.gramener.com/guide/translate/)
- [g1.sanddance](docs/sanddance.md) moves DOM elements smoothly based on data
- [g1.mapviewer](docs/mapviewer.md) renders leaflet maps and simplifies adding layers from data.
- Note: Mapviewer is not included in [g1.min.js](dist/g1.min.js). Include [mapviewer.min.js](dist/mapviewer.min.js)
## Utilities
- [g1.url.parse](docs/url.md) parses a URL into a structured object
- [url.join](docs/url.md#urljoin) joins two URLs
- [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
- [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)
- [g1.datafilter](docs/datafilter.md) filters the data based on the options
- [g1.types](docs/types.md) returns the data types of columns in a DataFrames
## Libraries
You can import either [g1.min.js](dist/g1.min.js) -- which has all of these functions --
or one of the individual libraries below:
- [ajax.min.js](dist/ajax.min.js)
- [datafilter.min.js](dist/datafilter.min.js)
- [event.min.js](dist/event.min.js)
- [formhandler.min.js](dist/formhandler.min.js)
- [highlight.min.js](dist/highlight.min.js)
- [leaflet.min.js](dist/leaflet.min.js)
- [sanddance.min.js](dist/sanddance.min.js)
- [search.min.js](dist/search.min.js)
- [template.min.js](dist/template.min.js)
- [translate.min.js](dist/translate.min.js)
- [types.min.js](dist/types.min.js)
- [urlchange.min.js](dist/urlchange.min.js)
- [urlfilter.min.js](dist/urlfilter.min.js)
[mapviewer.min.js](dist/mapviewer.min.js) is not part of [g1.min.js](dist/g1.min.js).
For debugging, use [dist/g1.js](dist/g1.js) -- an un-minified version.
[Read the documentation](https://learn.gramener.com/guide/g1/) for usage.
## Releases
......
# g1
`g1` is a micro-framework collection that makes it easy to write front-end data
applications. It's a humble alternative to [Vue.js](https://vuejs.org/),
[AngularJS](https://angularjs.org/) and [React](https://reactjs.org/).
It not a framework -- more a collection of tools that work together. Sort of
like UNIX.
If you need a library to build data applications without a learning curve and
low overhead, g1 may be for you.
## Installation
You can add g1 *and its dependencies* with a single script tag:
```html
<script src="https://cdn.jsdelivr.net/combine/npm/jquery,npm/lodash,npm/g1"></script>
```
or install it using:
```bash
yarn install jquery lodash g1
# ... OR ...
npm install jquery lodash g1
```
... and add this to your HTML file:
```html
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/lodash/lodash.min.js"></script>
<script src="node_modules/g1/dist/g1.min.js"></script>
```
## Render templates
You can create dynamic content from data using templates. To create a template:
1. Write HTML inside `<template>..</template>`.
2. Embed JavaScript inside `<% .. %>` in the template
3. Call [$(el).template(data)](template.md).
This renders your template with your data. For example:
<!-- render:html -->
```html
<template class="example">
<% list.forEach(function(item) { %>
<div>item <%= item %></div>
<% }) %>
</template>
<script>
$('template.example').template({ list: [1, 2, 3] })
</script>
```
Templates use [lodash](https://lodash.com/docs/#template), which is
plain HTML and JavaScript. You don't need to learn a new template language.
- Anything inside `<% ... %>` is executed as Javascript.
- Anything inside `<%= ... %>` is evaluated and displayed.
- To re-render the template, run `.template()` again with different data.
TODO: show a full-blown example of this.
## URL-based state
Apps shows different content based on what the user requests. A good way to
capture this is to store the requests in the URL. This lets the user bookmark
the page and share the URL. This URL represents the **state** of the app.
For example, `/view=sales&year=2019` may show the sales in year 2019.
### Parse URLs
To parse a URL, use [g1.url.parse](url.md):
```js
var url = g1.url.parse(location.href)
url.searchKey == {"view": "sales", "year": "2019"}
```
This can affect what template to render, and what data to render. For example:
```html
<template class="sales">Sales was <%- data %> in <%- q.year %>.</template>
<template class="profit">Profit was <%- data %> in <%- q.year %>.</template>
<script>
var data = {
sales: {2018: 250, 2019: 260},
profit: {2018: 50, 2019: 60}
}
var q = g1.url.parse(location.href).searchKey
$('template.' + q.view).template({ data: data[q.view][q.year], q: q })
</script>
```
When the URL is `?view=sales&year=2019`, it renders "Sales was 260 in 2019."
When the URL is `?view=profit&year=2018`, it renders "Profit was 50 in 2018."
TODO: show a full-blown example of this.
### Change URL state
To change the state, we change the query. For example:
- `?view=sales&year=2018` changes year to become...
- `?view=sales&year=2019`
We can do this using `<a href="?view=sales&year=2019">`. But we usually change
*only some of the keys* at a time (e.g. change year to 2019 *without* changing
`view`.)
[$().urlfilter()](urlfilter.md) makes this easier.
`<a class="urlfilter" href="?year=2019">` changes *just the year* to 2019, and
keeps other keys constant.
<!-- render:html -->
```html
<ul>
<li><a class="urlfilter" href="?view=sales">Set ?view=sales, keep the rest</a></li>
<li><a class="urlfilter" href="?view=profit">Set ?view=profit, keep the rest</a></li>
<li><a class="urlfilter" href="?year=2018">Set ?year=2018, keep the rest</a></li>
</ul>
<script>
$('body').urlfilter() // activates URL filtering on the entire page
</script>
```
TODO: show a full-blown example of this.
## Single page apps
Changing the URL reloads the page. To build single page apps that don't reload,
you can store the state in the location hash, like `/#?view=sales&year=2019`.
To make [$().urlfilter()](urlfilter.md) update the hash (instead of the query),
use `$('body').urlfilter({target: '#'})`.
To listen to hash change events, use [$(window).urlchange()](urlchange.md).
For example:
```js
$(window)
.on('#?view', function (e, view) { console.log('view is', view) })
.on('#?year', function (e, year) { console.log('year is', year) })
.on('#', function (e, url) { console.log('hash is', url) })
```
TODO: show a full-blown example of this.
### Virtual DOM
Normally, re-rendering a template deletes the content and re-creates the DOM
elements. But if you use `<template data-engine="vdom">`, it only updates
changed elements. This has 3 advantages:
- You can use CSS to animate the changes
- Any events bound to the DOM elements are preserved
- The DOM updates faster
TODO: show a full-blown example of this.
## Interactions
- [$.urlfilter](urlfilter.md) changes URL queries when clicked. Used to filter data.
- [$.urlchange](urlchange.md) listens to hash changes and routes events
- [$.search](search.md) highlights elements as you type in a search box
- [$.highlight](highlight.md) toggles classes on elements when clicked or hover
## Components
- [$.formhandler](formhandler.md) renders a HTML table from a [FormHandler URL](https://learn.gramener.com/guide/formhandler/)
- [$.template](template.md) renders lodash templates. Requires [lodash](https://lodash.com/)
- [$.dropdown](dropdown.md) renders a Bootstrap dropdown. Requires [Bootstrap](https://getbootstrap.com/docs/4.2/)
- [$.translate](translate.md) translates text to other languages using [Gramex translate](https://learn.gramener.com/guide/translate/)
- [g1.sanddance](sanddance.md) moves DOM elements smoothly based on data
- [g1.mapviewer](mapviewer.md) renders leaflet maps and simplifies adding layers from data.
- Note: Mapviewer is not included in [g1.min.js](dist/g1.min.js). Include [mapviewer.min.js](dist/mapviewer.min.js)
## Utilities
- [g1.url.parse](url.md) parses a URL into a structured object
- [url.join](url.md#urljoin) joins two URLs
- [url.update](url.md#urlupdate) updates a URL's query parameters
- [g1.fuzzysearch](fuzzysearch.md) searches for text with fuzzy matching
- [$.ajaxchain](ajaxchain.md) chains AJAX requests, loading multiple items in sequence
- [L.TopoJSON](topojson.md) loads TopoJSON files just like GeoJSON. Requires [topojson](https://github.com/topojson/topojson)
- [$.dispatch](dispatch.md) is like [trigger](https://api.jquery.com/trigger/) but sends a native event (triggers non-jQuery events too)
- [g1.datafilter](datafilter.md) filters the data based on the options
- [g1.types](types.md) returns the data types of columns in a DataFrames
## Libraries
You can import either [g1.min.js](dist/g1.min.js) -- which has all of these functions --
or one of the individual libraries below:
- [ajax.min.js](dist/ajax.min.js)
- [datafilter.min.js](dist/datafilter.min.js)
- [event.min.js](dist/event.min.js)
- [formhandler.min.js](dist/formhandler.min.js)
- [highlight.min.js](dist/highlight.min.js)
- [leaflet.min.js](dist/leaflet.min.js)
- [sanddance.min.js](dist/sanddance.min.js)
- [search.min.js](dist/search.min.js)
- [template.min.js](dist/template.min.js)
- [translate.min.js](dist/translate.min.js)
- [types.min.js](dist/types.min.js)
- [urlchange.min.js](dist/urlchange.min.js)
- [urlfilter.min.js](dist/urlfilter.min.js)
[mapviewer.min.js](dist/mapviewer.min.js) is not part of [g1.min.js](dist/g1.min.js).
For debugging, use [dist/g1.js](dist/g1.js) -- an un-minified version.
......@@ -5,28 +5,32 @@
Example:
```html
<script type="text/html">Your platform is <%= navigator.userAgent %></script>
<template>Your platform is <%= navigator.userAgent %></template>
<script>
$('body').template()
</script>
```
renders all `script[type="text/html"]` as [lodash templates](https://lodash.com/docs/#template).
renders all `template` or `script[type="text/html"]` elements 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 and rendered in-place.
Note: the `<template>` tag is not supported by Internet Explorer. Use
`<script type="text/html">` for IE compatibility.
The template can use all global variables. You can pass additional variables
using as `.template({var1: value, var2: value, ...})`. For example:
<!-- render:html -->
```html
<script type="text/html" class="example">
<template class="example">
<% list.forEach(function(item) { %>
<div><%= item %></div>
<% }) %>
</script>
</template>
<script>
$('script.example').template({list: ['a', 'b', 'c']})
</script>
......
......@@ -4,7 +4,9 @@
"description": "Gramex 1.x interaction library",
"license": "MIT",
"author": "S Anand <s.anand@gramener.com>",
"main": "index.js",
"main": "dist/g1.js",
"jsdelivr": "dist/g1.min.js",
"unpkg": "dist/g1.min.js",
"repository": {
"type": "git",
"url": "git@code.gramener.com:s.anand/g1.git"
......
......@@ -31,63 +31,64 @@ export default [
file: "dist/g1.min.js",
format: "umd",
name: "g1",
extend: true
extend: true,
sourcemap: true
},
plugins: [
resolve(),
commonjs(),
htmlparts('src/formhandler.template.html'),
htmlparts('src/dropdown.template.html'),
uglify()
uglify({ sourceMap: true })
]
},
{
input: "index-datafilter",
output: { file: "dist/datafilter.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/datafilter.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-urlfilter",
output: { file: "dist/urlfilter.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/urlfilter.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-formhandler",
output: { file: "dist/formhandler.min.js", format: "umd", name: "g1", extend: true},
output: { file: "dist/formhandler.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [
htmlparts('src/formhandler.template.html'),
process.env.npm_lifecycle_event == 'dev' ? '' : uglify()
process.env.npm_lifecycle_event == 'dev' ? '' : uglify({ sourceMap: true })
],
},
{
input: "index-highlight",
output: { file: "dist/highlight.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/highlight.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-template",
output: { file: "dist/template.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/template.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-translate",
output: { file: "dist/translate.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/translate.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-event",
output: { file: "dist/event.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/event.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-leaflet",
output: { file: "dist/leaflet.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/leaflet.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-mapviewer",
output: {
file: "dist/mapviewer.min.js", format: "umd", name: "g1", extend: true, globals: {
file: "dist/mapviewer.min.js", format: "umd", name: "g1", extend: true, sourcemap: true, globals: {
leaflet: 'L',
d3: 'd3'
}
......@@ -96,66 +97,58 @@ export default [
resolve(),
commonjs(),
babel(babelrc({ config: babelConfig, exclude: 'node_modules/**' })),
process.env.npm_lifecycle_event == 'dev' ? '' : uglify()
process.env.npm_lifecycle_event == 'dev' ? '' : uglify({ sourceMap: true })
],
// indicate which modules should be treated as external
external: ['leaflet', 'd3']
},
{
input: "index-sanddance",
output: { file: "dist/sanddance.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/sanddance.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-scale",
output: { file: "dist/scale.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()],
output: { file: "dist/scale.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })],
},
{
input: "index-urlchange",
output: { file: "dist/urlchange.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/urlchange.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-ajax",
output: { file: "dist/ajax.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/ajax.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-dropdown",
output: { file: "dist/dropdown.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [
resolve(),
commonjs(),
htmlparts('src/dropdown.template.html')
],
output: {
file: "dist/dropdown.min.js",
format: "umd",
name: "g1",
extend: true
}
htmlparts('src/dropdown.template.html'),
uglify({ sourceMap: true })
]
},
{
input: "index-search",
output: { file: "dist/search.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [
resolve(),
commonjs()
commonjs(),
uglify({ sourceMap: true })
],
output: {
file: "dist/search.min.js",
format: "umd",
name: "g1",
extend: true
}
},
{
input: "index-fuzzysearch",
output: { file: "dist/fuzzysearch.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/fuzzysearch.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
},
{
input: "index-types",
output: { file: "dist/types.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
output: { file: "dist/types.min.js", format: "umd", name: "g1", extend: true, sourcemap: true },
plugins: [uglify({ sourceMap: true })]
}
]
......@@ -2,7 +2,7 @@ import { findall } from './_util.js'
export function template(data, options) {
options = options || {}
var selector = options.selector || this.data('selector') || 'script[type="text/html"]'
var selector = options.selector || this.data('selector') || 'script[type="text/html"],template'
// Pre-create the template rendering function
// Store this in .data('template.function')
......@@ -22,19 +22,23 @@ export function template(data, options) {
var renderer = $this.data(_renderer)
// If there's no template function cached, cache it
if (!renderer) {
var html = $this.html()
// Contents of script are regular strings. Contents of template are escaped
if (!$this.is('script'))
html = _.unescape(html)
var src = $this.attr('src')
if (src) {
// If the AJAX load succeeds, render the loaded template
// Else render the contents, with an additional xhr variable
$.get(src)
.done(function (html) { make_template($this, html, data, options) })
.done(function (contents) { make_template($this, contents, data, options) })
.fail(function (xhr) {
data.xhr = xhr
make_template($this, $this.html(), data, options)
make_template($this, html, data, options)
})
} else
// If no src= is specified, just render the contents
make_template($this, $this.html(), data, options)
make_template($this, html, data, options)
} else
// If the renderer is already present, just use it
renderer(data, options)
......
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