Commit 2af532f1 authored by Tejesh's avatar Tejesh 🖖

merge dev

parent 59900777
Pipeline #78626 failed with stage
in 61 minutes and 11 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
......
......@@ -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.
......
......@@ -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`)
export { version } from './src/package.js'
export { fuzzysearch } from './src/fuzzysearch.js'
// export item into the g1.* namespace
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 { fuzzysearch } from './index-fuzzysearch.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
// import './index-mapviewer.js'
......
{
"name": "g1",
"version": "0.14.0",
"version": "0.15.0",
"description": "Gramex 1.x interaction library",
"license": "MIT",
"author": "S Anand <s.anand@gramener.com>",
......@@ -13,7 +13,7 @@
"lint": "eslint index*.js src && eclint check '**/*.html' '**/*.js' '**/*.css' '**/*.yaml' '**/*.md'",
"build": "rimraf dist && json2module package.json > src/package.js && rollup -c",
"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",
"test": "tape test/test-*.js | faucet && node test/server.js puppeteer | tap-merge | faucet",
"test-chrome": "node test/server.js chrome | tap-merge | faucet",
......
......@@ -148,6 +148,11 @@ export default [
extend: true
}
},
{
input: "index-fuzzysearch",
output: { file: "dist/fuzzysearch.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
},
{
input: "index-types",
output: { file: "dist/types.min.js", format: "umd", name: "g1", extend: true },
......
......@@ -368,23 +368,28 @@ function editHandler($this, template_data, options, template) {
var edit_btn = $('.edit button', $this)
var add_btn = $('.add button', $this)
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')
if (edited_rows.length > 0)
$('.loader', $this).removeClass('d-none')
var all_ajax = []
var allRowsValid = true
$.each(edited_rows, function (key, edited_row) {
var data = JSON.parse(edited_row.getAttribute('data-val'))
var rowIndex = edited_row.getAttribute('data-row')
for (key in data) {
// 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))
if (editable_element.length) {
data[key] = template_data['data'][rowIndex][key] = editable_element.val()
}
$('td[data-key="' + (remove_quotes(key)) + '"] :input', edited_row).each(function() {
if (this.checkValidity()) {
$(this).removeClass('is-invalid')
data[key] = template_data['data'][rowIndex][key] = $(this).val()
} else {
$(this).addClass('is-invalid')
allRowsValid = false
}
})
}
all_ajax.push(
$.ajax(options.src, {
method: 'PUT',
......@@ -397,10 +402,15 @@ function editHandler($this, template_data, options, template) {
})
)
})
if (!allRowsValid) return
$.when.apply($, all_ajax).then(function () {
$('.loader', $this).addClass('d-none')
edit_btn.html('Edit') // TODO: remove hardcoding of name Edit
add_btn.prop('disabled', false)
if (options.edit.done) options.edit.done()
})
template_data.isEdit = false
render_template('table', template_data, options, $this, template)
} else if (edit_btn.html().toLowerCase() == 'edit') {
......
......@@ -95,16 +95,21 @@ Each template receives these variables:
col_defaults(colinfo, data)
var menu_item = false
var col_id = idcount++
var qsort = parse('?')
var isSorted = _.includes(args['_sort'], colinfo.name) ? {op: '', cls: 'table-primary'} : _.includes(args['_sort'], '-' + colinfo.name) ? {op: '-', cls: 'table-danger'} : {}
%>
<th class="<%- args['_sort'] == colinfo.name ? 'table-primary' : args['_sort'] == '-' + colinfo.name ? 'table-danger' : '' %>" data-col="<%- colinfo.name %>">
<th class="<%- isSorted.cls %>" data-col="<%- colinfo.name %>">
<div class="dropdown">
<a href="#" class="dropdown-toggle text-nowrap" id="fh-dd-<%- col_id %>" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<%- colinfo.title || colinfo.name %>
</a>
<div class="dropdown-menu" aria-labelledby="fh-dd-<%- col_id %>">
<% _.each(colinfo.sort, function(title, op) { menu_item = true
var active = args['_sort'] == op + colinfo.name %>
<a class="dropdown-item urlfilter <%- active ? 'active': '' %>" href="?_sort=<%- active ? '' : op + colinfo.name %>">
qsort = qsort.update({_sort: args['_sort'] || []})
if (!_.isEmpty(isSorted))
qsort = qsort.update({_sort: [colinfo.name, '-' + colinfo.name]}, 'del')
var active = _.includes(args['_sort'], op + colinfo.name) %>
<a class="dropdown-item urlfilter <%- active ? 'active': '' %>" href="<%- qsort.update({_sort: [op + colinfo.name]}, active ? 'del': 'add').toString() %>">
<%- title %>
</a>
<% }) %>
......@@ -130,7 +135,7 @@ Each template receives these variables:
<div class="dropdown-divider"></div>
<% } %>
<% if (colinfo.hideable) { %>
<a class="dropdown-item urlfilter" href="?_c=-<%- colinfo.name %>" data-mode="add">Hide</a>
<a class="dropdown-item urlfilter" href="?_c=-<%- encodeURIComponent(colinfo.name) %>" data-mode="add">Hide</a>
<% } %>
</div><!-- .dropdown-menu -->
</div><!-- .dropdown -->
......@@ -189,7 +194,7 @@ Each template receives these variables:
</td>
<% } else { %>
<td>
<a class="urlfilter" href="?<%- colinfo.name %>=<%- val %>&amp;_offset=">
<a class="urlfilter" href="?<%- encodeURIComponent(colinfo.name) %>=<%- encodeURIComponent(val) %>&amp;_offset=">
<%= disp %>
</a>
</td>
......@@ -241,6 +246,11 @@ Each template receives these variables:
/>
<% } %>
<% if (isEditable.validationMessage) { %>
<div class="invalid-feedback">
<%- isEditable.validationMessage %>
</div>
<% } %>
<!-- end -->
<!-- var template_page -->
......@@ -350,7 +360,7 @@ Each template receives these variables:
qparts.update({_c: col_name}, 'add')
var hide_col = col_name[0] == '-'
var display_name = hide_col ? col_name.slice(1) : col_name %>
<a href="?_c=<%- col_name %>" data-mode="del" class="badge badge-pill <%- hide_col ? 'badge-dark' : 'badge-danger' %> urlfilter"
<a href="?_c=<%- encodeURIComponent(col_name) %>" data-mode="del" class="badge badge-pill <%- hide_col ? 'badge-dark' : 'badge-danger' %> urlfilter"
title="<%- hide_col ? 'Show' : 'Hide' %> column <%- display_name %>">
<%- display_name %>
</a>
......@@ -361,7 +371,7 @@ Each template receives these variables:
var update = {}
update[key] = col_name
qparts.update(update, 'add') %>
<a href="?<%- key %>=<%- col_name %>" data-mode="del" class="badge badge-pill badge-dark urlfilter" title="Clear <%- key %> filter">
<a href="?<%- encodeURIComponent(key) %>=<%- encodeURIComponent(col_name) %>" data-mode="del" class="badge badge-pill badge-dark urlfilter" title="Clear <%- key %> filter">
<%- key %> = <%- col_name %>
</a>
<% })
......@@ -419,7 +429,7 @@ Each template receives these variables:
var col_link = typeof colinfo.link == 'function' ? colinfo.link({row: row, value: val, index: rowIndex, name: colinfo.name, data: data, format: disp}) : _.template(colinfo.link)({row: row, value: val, index: rowIndex, name: colinfo.name, data: data, format: disp}) %>
<a href="<%- col_link %>" target="_blank"><%= disp %></a>
<% } else { %>
<a class="urlfilter" href="?<%- colinfo.name %>=<%- val %>&amp;_offset=">
<a class="urlfilter" href="?<%- encodeURIComponent(colinfo.name) %>=<%- encodeURIComponent(val) %>&amp;_offset=">
<%= disp %>
</a>
<% } %>
......
/*
var search = g1.fuzzysearch(data, options)
// Specify keys in data as a list of column names or functions
options.keys = [ 'col1', function(v) { return v.info.name } ]
// Specify max results to return. Default: 100
options.limit = 10
// Case sensitive search. Default: false
options.case = True
*/
export function fuzzysearch (data, options) {
options = options || {}
// "values" is the string array with the text to search
var values
// If no options.keys are provided, use the raw data as-is
if (!options.keys)
values = data
// Else, join the provided keys
else
values = data.map(function(row) {
return options.keys.map(function(v) {
return typeof v == 'function' ? v(row) : row[v]
}).join(' ')
})
var limit = options.limit || 100
var flags = options.case ? '' : 'i'
// TODO: document these options once stabilized
var depth = options.depth || 10
var escape = options.escape || true
return function(text) {
var results = [], // Final results
vals = values.slice(), // Values to search. Crosses off matches to avoid duplication
re
// Trim the search text
text = text.replace(/^\s/, '').replace(/\s$/, '')
if (escape)
text = text.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
// 1. Match full phrase
re = new RegExp(text.replace(/\s+/, '\\s+'), flags)
vals.forEach(function (v, i) {
if (v && re.test(v)) { results.push(data[i]); vals[i] = '' }
})
if (depth <= 1 || results.length >= limit) return results.slice(0, limit)
// 2. Match words in order
re = new RegExp(text.replace(/\s+/, '.*'), flags)
vals.forEach(function (v, i) {
if (v && re.test(v)) { results.push(data[i]); vals[i] = '' }
})
if (depth <= 2 || results.length >= limit) return results.slice(0, limit)
// 3. Match words in any order
re = text.split(/\s+/).map(function (word) { return new RegExp(word, flags) })
vals.forEach(function (v, i) {
if (v && re.every(function (word) { return word.test(v) })) { results.push(data[i]); vals[i] = '' }
})
if (depth <= 3 || results.length >= limit) return results.slice(0, limit)
// 4. Match partial words in any order
re = text.split(/\s+/).map(function (word) { return new RegExp(word.replace(/(.)/g, '$&[\\S]*'), flags) })
vals.forEach(function (v, i) {
if (v && re.every(function (word) { return word.test(v) })) { results.push(data[i]); vals[i] = '' }
})
// 5. Match characters in order
re = new RegExp(text.replace(/(.)/g, '$&.*'), flags)
vals.forEach(function (v, i) {
if (v && re.test(v)) { results.push(data[i]); vals[i] = '' }
})
if (depth <= 4 || results.length >= limit) return results.slice(0, limit)
return results.slice(0, limit)
}
}
Continent,Cross,ID,Name,Shapes,Stripes,Symbols,Text,Union-Flag,c1,c2,c3,c4,c5,c6,c7,c8,date col
Continent,Cross,ID,Name,Shapes,Stripes,Symbols,Text&,Union-Flag,c1,c2,c3,c4,c5,c6,c7,c8,date col
Europe,,AND,Andorra,,Horizontal,,,,35,1,26,0,32,0,0,4,16-01-2013
Asia,,ARE,United Arab Emirates,,Horizontal,,,,24.0,0.0,0.0,25.0,0.0,0.0,25.0,24.0,17-02-2013
Asia,,AFG,Afghanistan,,Vertical,,Country,,28.0,1.0,0.0,33.0,0.0,0.0,33.0,3.0,06-02-2013
Asia,,AFG,Afghanistan,,Vertical,,Country&,,28.0,1.0,0.0,33.0,0.0,0.0,33.0,3.0,06-02-2013
North America,,ATG,Antigua Barbuda,,,,,,50.0,0.0,5.0,0.0,0.0,10.0,25.0,7.0,11-02-2013
Europe,,ALB,Albania,,,Bird,,,87.0,0.0,0.0,0.0,0.0,0.0,12.0,0.0,18-01-2013
Asia,,ARM,Armenia,,Horizontal,,,,33.0,33.0,0.0,0.0,33.0,0.0,0.0,0.0,12-01-2013
......
This diff is collapsed.
......@@ -71,8 +71,10 @@
attrs: {
min: 10,
max: 100,
required: '',
placeholder: '0 - 100'
}
},
validationMessage: 'Number must be between 0-100 and is mandatory'
}
},
{
......@@ -112,9 +114,9 @@
}
},
{
name: 'delete',
name: 'Actions',
template: function(row) {
return "<td><button data-action='delete'><i class='fa fa-trash'></i></button></td>"
return "<td><button data-action='edit'><i class='fa fa-trash'></i></button></td>"
},
}
],
......@@ -144,20 +146,46 @@
$('.edit button').click()
// make sure the initial value is Europe
test.equals($(".edit-fh table > tbody > tr:nth-child(1) > td:nth-child(2) > input").val().trim(), init_cell_value)
$(".edit-fh table > tbody > tr:nth-child(1) > td:nth-child(3) > input").val('300000').trigger('change')
$(".edit-fh table > tbody > tr:nth-child(3) > td:nth-child(3) > input").val('-90').trigger('change')
// save row
$('.edit button').click()
test.equals($('.is-invalid').val(), '300000')
$(".edit-fh table > tbody > tr:nth-child(1) > td:nth-child(3) > input").val('35').trigger('change')
// modify cell value inside <input>
$(".edit-fh table > tbody > tr:nth-child(1) > td:nth-child(2) > input").val('Edited')
$(".edit-fh table > tbody > tr:nth-child(1) > td:nth-child(2) > input").trigger('change')
$(".edit-fh table > tbody > tr:nth-child(1) > td:nth-child(2) > input").val('Edited').trigger('change')