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
- `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`:
- 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:
- [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)
......@@ -72,14 +73,6 @@ For debugging, use [dist/g1.js](dist/g1.js) -- an un-minified version.
[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
Every release is tested on the current versions of Chrome, Edge and Firefox.
......
......@@ -3,9 +3,14 @@
`$.dropdown()` creates dropdowns that integrate well with
[$.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.
```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:
```html
......
......@@ -30,75 +30,70 @@ The full list of options is below. Simple options can be specified as `data-` at
- `title`: for header display. Defaults to the same value as `name`
- `type`: `text` (default) / `number` / `date`. Data type. Determines filters to be used
- `format`: string / function that returns formatted cell display value.
- function accepts an object with these keys:
- `name`: column name
- `value`: cell data value
- `row`: row data
- `index`: row index
- `data`: the dataset from `src`
- strings specify a numeral.js format if the value is a number (you must include numeral.js)
- strings specify a moment.js format if the value is a date (you must include moment.js)
- `editable`: `true` (default) / `false`. When `true`, edit and save buttons appears at end of each row.
- To bind UI input element such as dropdown, datepicker, radio etc., `editable` accepts an object with these keys.
- function accepts an object with these keys:
- `name`: column name
- `value`: cell data value
- `row`: row data
- `index`: row index
- `data`: the dataset from `src`
- strings specify a numeral.js format if the value is a number (you must include numeral.js)
- strings specify a moment.js format if the value is a date (you must include moment.js)
- `editable`: `true` (default) / `false`. When `true`, edit and save buttons appears at end of each row. To bind UI input element such as dropdown, datepicker, radio etc., `editable` accepts an object with these keys.
- `input`: **Mandatory**. The type of input element to use. The valid values are checkbox, radio, range, select, and any other legal [HTML form input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
- `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:
`input: 'number', attrs: {step: 10, placeholder: '0 - 1000', name: 'some_name'}` would render as
`<input step=10 placeholder="0 - 1000" name="some_name" />`
- `attrs`: To place common attributes such as max, min, placeholder, name etc., on the input.
Example: `{placeholder: "Age", max:100}` renders `<input placeholder="Age" max="100">`
- `validationMessage`: The message to be shown when invalid input is entered.
Example: `"Age must be less than 100"`
- `template`: string template / function that renders the cell.
- function accepts an object with these keys:
- `name`: column name
- `value`: cell data value
- `format`: formatted cell display value
- `link`: cell link value (if applicable)
- `index`: row index
- `row`: row data
- `data`: the dataset from `src`
- string template can use the above variables
- function accepts an object with these keys:
- `name`: column name
- `value`: cell data value
- `format`: formatted cell display value
- `link`: cell link value (if applicable)
- `index`: row index
- `row`: row data
- `data`: the dataset from `src`
- string template can use the above variables
- `sort`: `true` / `false` / operators dict with:
- `{'': 'Sort ascending', '-': 'Sort descending'}` (default)
- `{'': 'Sort ascending', '-': 'Sort descending'}` (default)
- `filters`: `true` (default) / `false` / operators dict with:
- `{'', 'Equals...', '!', 'Does not equal...', ...}`.
The default list of operators is based on the auto-detected type of the column.
- `{'', 'Equals...', '!', 'Does not equal...', ...}`. The default list of operators is based on the auto-detected type of the column.
- `link`: string / function that generates a link for this each cell.
- If no `link:` is specified, clicking on the cell filters by that cell.
- If `link:` is a function, opens a new window with the URL as `fn(args)`.
- function accepts an object with these keys:
- `name`: column name
- `value`: cell data value
- `format`: formatted cell display value
- `index`: row index
- `row`: row data
- `data`: the dataset from `src`
Example: `function(args) { return 'https://example.org/city/' + args.value }`
- If `link:` is a string, opens a new window with the string URL interpolated as a lodash template with an object (mentioned above) as data.
Example: `"https://example.org/city/<%- value >"`
- If no `link:` is specified, clicking on the cell filters by that cell.
- If `link:` is a function, opens a new window with the URL as `fn(args)`.
- function accepts an object with these keys:
- `name`: column name
- `value`: cell data value
- `format`: formatted cell display value
- `index`: row index
- `row`: row data
- `data`: the dataset from `src`
- Example: `function(args) { return 'https://example.org/city/' + args.value }`
- If `link:` is a string, opens a new window with the string URL interpolated as a lodash template with an object (mentioned above) as data.
Example: `"https://example.org/city/<%- value >"`
- `hideable`: `true` (default) / `false`. Show or hide `Hide` option in header dropdown
- `hide`: `true` / `false` (default). Hides the column
- `unique`: list of values. Adds a list of checkboxes of unique values to filter on, in the column header dropdown.
- `edit`: Shows the Edit control. Can be `true` / `false` (default). Can also pass an object.
- `done`: function that gets called after saving the edited row.
- `done`: function that gets called after saving the edited row.
- `add`: Show the Add control. Can be `true` / `false` (default). Can also pass an object.
- `done`: function that gets called after saving the new row.
- `done`: function that gets called after saving the new row.
- `actions`: A list of objects. you need not add it to actions
- `{{action}}`: a function() that gets triggered on clicking the element with `data-action='{{action}}` attribute. The value of `data-action` attribute must match with key `{{action}}` in `actions`.
- function accepts an object with these keys:
- `row`: row data
- `index`: index of the row in the dataset from `src`
- `notify(message)`: a function that shows a notification
- If the return value can be a jQuery deferred (e.g. `$.ajax`), it shows a loading indicator and a success / failure message when the deferred is resolved. Example:
- `highlight_row`: `function(obj) { $(obj.row).addClass('.yellow_color')}`. Either a new column can be defined in `columns:` (example: {`name`: `Additional Col`}) with cell_template having an element with data attribute as `data-action='highlight_row'` or can use an existing column but with custom template that has an element with data attribute as `data-action='highlight_row'`.
- Note: DELETE operation is executed on a row if an element has data attribute `data-action='delete'`. If `delete` action is given in `actions`, the function given for `delete` is executed on click of an element with `data-action='delete'` instead od executing DELETE operation.
- `{{action}}`: a function() that gets triggered on clicking the element with `data-action='{{action}}` attribute. The value of `data-action` attribute must match with key `{{action}}` in `actions`.
- function accepts an object with these keys:
- `row`: row data
- `index`: index of the row in the dataset from `src`
- `notify(message)`: a function that shows a notification
- If the return value can be a jQuery deferred (e.g. `$.ajax`), it shows a loading indicator and a success / failure message when the deferred is resolved. Example:
- `highlight_row`: `function(obj) { $(obj.row).addClass('.yellow_color')}`. Either a new column can be defined in `columns:` (example: {`name`: `Additional Col`}) with cell_template having an element with data attribute as `data-action='highlight_row'` or can use an existing column but with custom template that has an element with data attribute as `data-action='highlight_row'`.
- Note: DELETE operation is executed on a row if an element has data attribute `data-action='delete'`. If `delete` action is given in `actions`, the function given for `delete` is executed on click of an element with `data-action='delete'` instead od executing DELETE operation.
- `onhashchange`: `true` re-renders table on hashchange based on filters in URL
hash. Set `false` to disable listening to hashchange (default `true`)
- `table`: Shows the table control. Can be:
- `true`: displays a table (default)
- `'grid'`: renders a grid instead of a table
- `false`: disables the table (and shows nothing for the main content)
- `true`: displays a table (default)
- `'grid'`: renders a grid instead of a table
- `false`: disables the table (and shows nothing for the main content)
- `count`: Shows the number of rows. Can be `true` (default) / `false`
- `page`: Shows the page control. Can be `true` (default) / `false`.
- `pageSize`: page size. Defaults to 100
......@@ -115,13 +110,13 @@ The full list of options is below. Simple options can be specified as `data-` at
- returns a dict with modified values of `data` and `meta`
- `icon`: if `table: 'grid'` is used, display an icon. string / function that renders the cell.
- function accepts an object with these keys:
- `row`: row data
- `data`: the dataset from `src`
- `index`: index of the row in the dataset from `src`
Example:
- `icon: 'fa fa-home fa-3x'` renders a FontAwesome home icon
- `icon: './path/to/image.png'` renders the image specified
- `icon: function(args) { return args.row['image_link'] }` renders an image with `src` attribute as the value from column name `image_link`
- `row`: row data
- `data`: the dataset from `src`
- `index`: index of the row in the dataset from `src`
- Example:
- `icon: 'fa fa-home fa-3x'` renders a FontAwesome home icon
- `icon: './path/to/image.png'` renders the image specified
- `icon: function(args) { return args.row['image_link'] }` renders an image with `src` attribute as the value from column name `image_link`
**Advanced**. Each component can have a target which specifies a selector. For
e.g., to render the export button somewhere else, use
......@@ -141,6 +136,7 @@ targets are:
`data-search-template="<input type='search'>"` will replace the search template
with a simple input. Available template strings are:
- `tableTemplate`
- `table_gridTemplate`
- `countTemplate`
- `pageTemplate`
- `sizeTemplate`
......@@ -174,7 +170,7 @@ Features to be implemented:
## $.formhandler examples
Render a table using the FormHandler at `./data`:
### Render from a FormHandler
```html
<div class="formhandler" data-src="./data"></div>
......@@ -183,22 +179,20 @@ Render a table using the FormHandler at `./data`:
</script>
```
Get data inside formhandler table:
### Access data inside formhandler
```html
<div class="formhandler" data-src="./data"></div>
<script>
$('.formhandler')
.on('load', function(formdata, meta, args, options) {
console.log('data inside formhandler table: ', formdata) // gives data loaded in to formhandler table
.on('load', function(data, meta, args, options) {
console.log('data inside formhandler table: ', data)
})
.formhandler()
</script>
```
Customize cell rendering to display a chart in a cell:
## Draw chart in cell
```html
<div class="formhandler" data-src="./data"></div>
......@@ -217,7 +211,7 @@ Customize cell rendering to display a chart in a cell:
</script>
```
In edit mode, show HTML input bindings like Dropdown, Datepicker, Number fields.. :
### Customize inputs in edit mode
```html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css"/>
......@@ -241,11 +235,14 @@ In edit mode, show HTML input bindings like Dropdown, Datepicker, Number fields.
name: 'c1',
editable: {
input: 'number',
attrs: { // keys and values in `attrs` will be added as <input type="number" min=10 max=100 placeholder="0 - 100"/>
// keys and values in `attrs` will be added as
// <input type="number" min=10 max=100 placeholder="Age"/>
attrs: {
min: 10,
max: 100,
placeholder: '0 - 100'
}
placeholder: 'Age'
},
validationMessage: 'Age must be between 0-100'
}
},
{
......
# 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
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">
<script type="text/html" class="example">
<% list.forEach(function(item) { %>
<div><%= item %></div>
<% }) %>
</script>
<script>
$('body').template({list: ['a', 'b', 'c']})
$('script.example').template({list: ['a', 'b', 'c']})
</script>
```
......@@ -45,21 +46,22 @@ for this to work.
For example, this shows a circle in SVG bouncing around smoothly.
<!-- render:html -->
```html
<style>
circle { transition: all 1s ease; }
</style>
<script src="../node_modules/morphdom/dist/morphdom-umd.min.js"></script>
<script type="text/html">
<svg width="100" height="100">
<script src="../../ui/morphdom/dist/morphdom-umd.min.js"></script>
<script type="text/html" data-engine="vdom" class="bouncing-ball">
<svg width="500" height="50">
<circle cx="<%= x %>" cy="<%= y %>" r="5" fill="red"/>
</svg>
</script>
<script>
setInterval(function() {
var x = Math.random() * 100
var y = Math.random() * 100
$('body').template({x: x, y: y}) // Update the template to animate
var x = Math.random() * 500
var y = Math.random() * 50
$('.bouncing-ball').template({x: x, y: y}) // Update the template to animate
}, 1000)
</script>
```
......@@ -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
once and apply them across the body. For example:
```js
$('script.chart')
.template({heading: 'Dashboard 1'}, {target: '.dashboard1'})
.template({heading: 'Dashboard 2'}, {target: '.dashboard2'})
.template({}, {target: '.no-heading'})
<!-- render:html -->
```html
<div class="panel1 bg-primary text-white px-3"></div>
<div class="panel2 bg-success text-white px-3"></div>
<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
template. This is the same as specifying `{target: '.dashboard'}`. For example:
The target can also be specified via a `data-target=".panel1"` on the script
template. This is the same as specifying `{target: '.panel'}`. For example:
```html
<script class="chart" data-target=".dashboard1">...</script>
<script class="chart" data-target=".dashboard2">...</script>
<script class="chart" data-target=".panel1">...</script>
<script class="chart" data-target=".panel2">...</script>
```
## $.template append
To append instead of replacing, run `.template(data, {append: true})`. Every
time `.template` is called, it appends rather than replaces. For example:
To append instead of replacing, use `data-append="true"`. Every time `.template`
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')
.template({heading: 'Item 1'}, {append: true}), // Appends the heading
.template({heading: 'Item 2'}, {append: true}), // instead of replacing it
.template({n: 1})
.template({n: 2})
</script>
```
You can also specify this as `<script data-append="true">`. This helps append to
an existing target. For example:
You can also specify this as `.template(data, {append: true})`. You can also
append to an [existing target](#template-targets). For example:
<!-- render: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>
<!-- Every time .template() is called, the result is added as a list item here -->
</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:
Template containers can have an `src=` attribute that loads the template from a file:
<!-- render:html -->
```html
<script type="text/html" src="template.html"></script>
<script type="text/html" src="template.html" class="source"></script>
<script>
$('body').template()
$('script.source').template()
</script>
```
......@@ -135,36 +156,43 @@ as a template. The template can use:
For example:
<!-- render:html -->
```html
<script type="text/html" src="missing.html">
Template returned error code: <%= xhr.status %>.
<script type="text/html" src="missing.html" class="missing">
Template returned HTTP error code: <%= xhr.status %>.
Data is <%= data %>
</script>
<script>
$('body').template({data: data})
$('script.missing').template({data: [1, 2, 3]})
</script>
```
## $.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
example:
<!-- render:html -->
```html
<section data-selector="script.lodash-template">
<script class="lodash-template">...</script>
<section data-selector=".render">
<script type="text/html" class="no-render">This will not render</script>
<script type="text/html" class="render">This will render</script>
</section>
<script>
$('section').template()
$('section[data-selector]').template()
</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
<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.lodash-template').template()
$('script.try').template({}, {selector: '.render', target: '.selector-target'})
</script>
```
......@@ -177,12 +205,17 @@ You can also render a template by selecting it directly. For example:
For example:
```js
$('script[type="text/html"]')
.on('template', function(e) { // Returns nodes rendered by the template
e.target // Get the target nodes
.filter('div') // Filter all <div> elements inside
.attr('class', 'item') // Change their class
<!-- render:html -->
```html
<script type="text/html" class="event">
<pre>Event e.templatedata = <span class="data">filled by event handler</span></pre>
</script>
<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:
- When the URL changes from `#a` to `#b`, it triggers 2 events on `window`:
- `.on('#', function(e) { e.hash.pathname == 'b' }`
- `.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
......
......@@ -4,8 +4,9 @@
Example: Let's say the following HTML is on the page `/?city=NY`.
<!-- render:html -->
```html
<a class="urlfilter" href="?name=John">Link</a>
<a class="urlfilter" href="?name=John">Add ?name=John to URL</a>
<script>
$('body').urlfilter()
</script>
......@@ -26,21 +27,51 @@ current page URL instead of replacing it.
For example:
<!-- render:html -->
```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>
<li><a class="urlfilter" href="?city=NY"> Change ?city= to NY</a></li>
<li><a class="urlfilter" href="?city=NY" data-mode="add"> Add ?city= to NY</a></li>
<li><a class="urlfilter" href="?city=NY" data-mode="del"> Remove NY from ?city=</a></li>
<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>
<li><a class="urlfilter" href="?city=NY" data-target="#"> Change location.hash, i.e. #?city= to NY</a></li>
```
This works with `input`, `select` and `form` elements as well.
<!-- render:html -->
```html
<p><label><input type="checkbox" class="urlfilter" name="a" value="1" data-mode="toggle" data-target="#"> a=1</label></p>
<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="#">