1
0
Fork 0
mirror of https://github.com/koalyptus/TableFilter.git synced 2024-06-28 02:10:56 +02:00

Compare commits

..

No commits in common. "master" and "v0.1.14" have entirely different histories.

235 changed files with 8372 additions and 43209 deletions

View file

@ -1,15 +0,0 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# Trailing whitespace is significant in markdown files.
[*.md]
trim_trailing_whitespace = false

View file

@ -1,14 +0,0 @@
{
"source": "src",
"destination": "docs/docs",
"plugins": [{
"name": "esdoc-standard-plugin",
"option": {
"lint": {"enable": false},
"accessor": {
"access": ["public", "protected"],
"autoPrivate": true
}
}
}]
}

View file

@ -1,37 +0,0 @@
{
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": { "modules": true }
},
"rules": {
"max-len": [2, 80, 2, {"ignoreUrls": true}],
"indent": [2, 4, {"SwitchCase": 1}],
"semi": ["error", "always"],
"no-trailing-spaces": 2,
"no-multi-spaces": 2,
"array-bracket-spacing": 2,
"keyword-spacing": ["error", { "after": true, "before": true }],
"max-depth": [2, 7],
"max-statements": [2, 133],
"complexity": [2, 45],
"no-unused-vars": 2,
"no-eval": 2,
"no-underscore-dangle": 0,
"no-loop-func": 2,
"no-floating-decimal": 2,
"curly": 2,
"eqeqeq": [2, "smart"],
"quotes": [2, "single"],
"new-cap": 2,
"radix": [2, "always"]
},
"env": {
"es6": true,
"browser": true,
"node": false
},
"globals": {
"__webpack_public_path__": false
}
}

View file

@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Reproduction steps:**
Steps to reproduce the behavior:
1. [First Step]
2. [Second Step]
3. [Other Steps...]
**Expected behavior**
A clear and concise description of what you expected to happen.
**Observed behavior:**
A clear and concise description of observed behavior.
**Screenshots**
If applicable, add screenshots to help explain your problem.
![Screenshots and GIFs which follow reproduction steps to demonstrate the problem](url)
**TableFilter version:** [Enter TableFilter version here]
**Browser and version:** [Enter Browser name and version here]
**OS and version:** [Enter OS name and version here]
**Device:** [e.g. iPhone6]
**Additional information/context:**
Add any other context about the problem here.
* Problem started happening recently, didn't happen in an older version of TableFilter: [Yes/No] if yes [version here]
* Problem can be reliably reproduced, doesn't happen randomly: [Yes/No]

View file

@ -1,36 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Enhancement/feature description**
[A clear and concise description of what the problem is. Ex. I'm always frustrated when ...]
**Steps which explain the enhancement/feature**
1. [First Step]
2. [Second Step]
3. [Other Steps...]
**Current and suggested behavior**
[Describe current and suggested behavior here]
**Why would the enhancement be useful to most users**
[Explain why the enhancement would be useful to most users]
**Screenshots and GIFs**
![Screenshots and GIFs which demonstrate the steps or part of TableFilter the enhancement suggestion is related to](url)
**Additional context**
[Add any other context or screenshots about the feature request here.]
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**TableFilter Version:** [Enter TableFilter version here]
**Browser and version:** [Enter Browser name and version here]
**OS and version:** [Enter OS name and version here]
**Device:** [e.g. iPhone6]

View file

@ -1,17 +0,0 @@
---
name: Support
about: I need support with TableFilter
title: ''
labels: ''
assignees: ''
---
For usage and support questions, please check out resources below, you might find an answer:
- https://www.tablefilter.com/
- https://www.tablefilter.com/docs/
- https://www.tablefilter.com/examples.html
- https://github.com/koalyptus/TableFilter/wiki/
- https://github.com/koalyptus/TableFilter/issues?q=is%3Aissue+is%3Aclosed
- https://codepen.io/koalyptus/pens/public/

3
.gitignore vendored
View file

@ -1,10 +1,9 @@
demos
docs
node_modules
report
.grunt
npm-debug.log
*.js.map
.codio
.settings
.vscode

17
.jshintrc Normal file
View file

@ -0,0 +1,17 @@
{
"-W024": true,
"scripturl": true,
"evil": true,
"curly": true,
"indent": 4,
"es3": true,
"esnext": true,
"unused": true,
"maxlen" : 80,
"trailing": true,
"quotmark": "single",
"immed": true,
"maxstatements": 172,
"maxdepth": 7,
"maxcomplexity": 87
}

View file

@ -1,53 +1,13 @@
language: node_js
node_js:
- '8.9.4'
- '0.10'
before_script:
- npm install grunt-cli -g
- npm install codecov -g
- npm install grunt-cli -g
script:
- npm test
- npm run codecov
- npm run build:all
- grunt test deploy
branches:
only:
- master
- /^greenkeeper/.*$/
before_deploy:
- git config --global user.email "maxgug@hotmail.com"
- git config --global user.name "koalyptus"
- export GIT_TAG=$(git describe --tags --abbrev=0)
- echo -n $GIT_TAG
- export NEW_GIT_TAG=$(node -p -e "require('./package.json').version")
- echo -n $NEW_GIT_TAG
- |
if [ $GIT_TAG != $NEW_GIT_TAG ]; then
git tag $NEW_GIT_TAG -am "Generated tag from TravisCI build $TRAVIS_BUILD_NUMBER"
fi
deploy:
- provider: releases
on:
branch: master
api_key: $GITHUB_OAUTH_ACCESS_TOKEN
skip_cleanup: true
name: $NEW_GIT_TAG
- provider: s3
on:
branch: master
access_key_id: $AWS_ACCESS_KEY_ID
secret_access_key: $AWS_SECRET_ACCESS_KEY
region: $AWS_DEFAULT_REGION
bucket: www.tablefilter.com
skip_cleanup: true
local_dir: dist
- provider: s3
on:
branch: master
access_key_id: $AWS_ACCESS_KEY_ID
secret_access_key: $AWS_SECRET_ACCESS_KEY
region: $AWS_DEFAULT_REGION
bucket: www.tablefilter.com
skip_cleanup: true
local_dir: docs
env:
global:
- secure: A1G8GvJkV0rjy7XCTVdOpTHy3xaoSZZAbMWhI+ikrqBqd8mRz+sB71FhRusouTcYdsT5VfF9Io2doS8LKAeP0TNC34Pp0uvjtsvarzn8a/oNEOuqR3Ub0ws2bmbZIZc+wOpgErKOj1H1QSJAUpd6ZjIuEAbOVXlhGBJz3zUCmcpRDh32CpFKC62oFWeGlvttxPciLLzBfKgkVKEGhPtdGP/xCHL1MCQptYVHZiXwWsaIQ5wHFO6KCVlRrPgdfOL+Yce3mT02iXH6ZjW6U6zA6vYQVQZVD873AkU5RmirYblW+jW1wdvu4UXI71lSH6Z3uXRVnrw1b0TsLVTjP9ZUbCtkTHtLbxYzeDjEukxKoCjpAppIhOtaNIxrdA8oKJAabQYp5X+QK6lkosy0zdT5u2B1+g8unZhsf0y//7lgLUe04iQ7sc1Q6AHiiEGtByaXg4BHNW53bUfKgNnbV4+IbXf8rz5wWOxL2/yWAU/GoiszjqRQfajAXCpSf6SyMjXjhhvQdeFn+Cz6FwdtaxH+tOIY0Hq9Gqy1xrLIkv/httd3O+AbhLrU1c/M0MwlFQue7GeJb7ZyF3KsK7bXvoz2dEqvzHd98NZXiQEqFXCIs77uVh4eZMoYrbEyrkOAgkUZNQYhHh9fuvfynJ/zgUvyA0v3GUvBebq3ybYKD/vqX7s=

View file

@ -1,128 +0,0 @@
# Contributing to TableFilter
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
## Reporting bugs
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/).
Create an issue and provide the following information.
Explain the problem and include additional details to help maintainers reproduce
the problem:
* **Use a clear, concise and descriptive title** for the issue to identify the
problem.
* **Describe the exact steps which reproduce the problem** in as many details
as possible.
* **Provide specific examples to demonstrate the steps**. Include links to
files or GitHub projects, or copy/pasteable snippets, which you use in those
examples. If you're providing snippets in the issue, use
[Markdown code blocks](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown).
Do not paste large snippets completely unrelated to the issue.
* **Describe the behavior you observed after following the steps** and point
out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* Do not hesitate to **include screenshots and animated GIFs** which show you
following the described steps and clearly demonstrate the problem.
You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on macOS
and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or
[this tool](https://github.com/GNOME/byzanz) on Linux.
### Template for submitting bug reports
[Short description of problem here]
**Reproduction steps:**
1. [First Step]
2. [Second Step]
3. [Other Steps...]
**Expected behavior:**
[Describe expected behavior here]
**Observed behavior:**
[Describe observed behavior here]
**Screenshots and GIFs**
![Screenshots and GIFs which follow reproduction steps to demonstrate the
problem](url)
**TableFilter version:** [Enter TableFilter version here]
**Browser and version:** [Enter Browser name and version here]
**OS and version:** [Enter OS name and version here]
**Additional information:**
* Problem started happening recently, didn't happen in an older version of
TableFilter: [Yes/No]
* Problem can be reliably reproduced, doesn't happen randomly: [Yes/No]
## Suggesting enhancements and features
Enhancement and feature suggestions are tracked as
[GitHub issues](https://guides.github.com/features/issues/).
Create an issue and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the
suggestion.
* **Provide a step-by-step description of the suggested enhancement/feature**
in as many details as possible.
* When applicable **describe the current behavior** and
**explain which behavior you expected to see instead** and why.
* **Include screenshots and animated GIFs** which help you demonstrate the
steps or point out the part of TableFilter which the suggestion is related to.
You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on macOS
and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or
[this tool](https://github.com/GNOME/byzanz) on Linux.
* **Explain why this enhancement would be useful** to most TableFilter users.
### Template for submitting enhancement and feature suggestions
[Short description of suggestion]
**Steps which explain the enhancement/feature**
1. [First Step]
2. [Second Step]
3. [Other Steps...]
**Current and suggested behavior**
[Describe current and suggested behavior here]
**Why would the enhancement be useful to most users**
[Explain why the enhancement would be useful to most users]
**Screenshots and GIFs**
![Screenshots and GIFs which demonstrate the steps or part of TableFilter
the enhancement suggestion is related to](url)
**TableFilter Version:** [Enter TableFilter version here]
**Browser and version:** [Enter Browser name and version here]
**OS and version:** [Enter OS name and version here]
## Code contribution
TableFilter welcomes contributions from anyone and everyone. If you want to get
your hands dirty:
### Pull requests
In general, we follow the "fork-and-pull" Git workflow.
1. **Fork** the repo on GitHub
2. **Clone** the project to your own machine
3. **Commit** changes to your own branch
- 3.1. **Add** unit tests covering your change in the `test` folder
- 3.2. **Run** the tests with the `npm run eslint test` command
- 3.3. **Ensure** build is working with `npm run dist` command
4. **Push** your work back up to your fork
5. Submit a **Pull request** so that we can review your changes
NOTE: Be sure to merge the latest from "upstream" before making a pull request!
## Donations
Support this project by donating [here](http://www.tablefilter.com/donate.html).
Thanks to all those who supported this project in the past and those who will
in the future!

View file

@ -1,26 +1,28 @@
module.exports = function (grunt) {
var webpack = require('webpack');
var webpackConfig = require('./webpack.config.js');
var fs = require('fs');
var path = require('path');
var testDir = 'test';
var testHost = 'http://localhost:8000/';
var pkg = grunt.file.readJSON('package.json');
var repo = 'github.com/koalyptus/TableFilter';
grunt.initConfig({
qunit: {
jshint: {
src: [
'Gruntfile.js',
'webpack.config.js',
'src/**/*.js'
],
options: {
'--web-security': 'no',
coverage: {
disposeCollector: true,
src: ['dist/tablefilter/*.js'],
instrumentedFiles: 'report/temp/',
htmlReport: 'report/coverage',
coberturaReport: 'report/',
lcovReport: 'report/',
jsonReport: 'report',
linesThresholdPct: 80
}
},
jshintrc: '.jshintrc'
}
},
qunit: {
all: {
options: {
urls: getTestFiles(testDir, testHost)
@ -80,21 +82,21 @@ module.exports = function (grunt) {
{
pattern: /{NAME}/ig,
replacement: pkg.name
}, {
},{
pattern: /{VERSION}/ig,
replacement: pkg.version
}, {
},{
pattern: /{EZEDITTABLE_LINK}/ig,
replacement: '<a href="http://edittable.free.fr/' +
'zip.php?f=ezEditTable.zip&amp;p=1"' +
'target="_blank" title="ezEditTable is a ' +
'javascript code aimed at enhancing regular ' +
'HTML tables by adding features such as ' +
'inline editing components, advanced ' +
'selection and keyboard navigation ' +
'- Developed by ' + pkg.author.name + '">' +
'ezEditTable</a>'
}, {
'zip.php?f=ezEditTable.zip&amp;p=1"' +
'target="_blank" title="ezEditTable is a ' +
'javascript code aimed at enhancing regular ' +
'HTML tables by adding features such as ' +
'inline editing components, advanced ' +
'selection and keyboard navigation ' +
'- Developed by '+ pkg.author.name +'">' +
'ezEditTable</a>'
},{
pattern: /<!-- @import (.*?) -->/ig,
replacement: function (match, p1) {
return grunt.file.read('static/' + p1);
@ -107,6 +109,26 @@ module.exports = function (grunt) {
clean: ['demos/starter.html'],
'webpack-dev-server': {
options: {
webpack: webpack.dev,
publicPath: '/dist/'
},
start: {
keepAlive: true,
webpack: {
devtool: 'eval',
debug: true
}
}
},
webpack: {
options: webpackConfig,
build: webpackConfig.build,
dev: webpackConfig.dev
},
watch: {
app: {
files: ['src/**/*', 'static/style/**/*'],
@ -124,79 +146,202 @@ module.exports = function (grunt) {
}
},
// temporary shell commands while decommissioning grunt
shell: {
eslint: {
command: 'npm run lint'
babel: {
options: {
sourceMap: true,
modules: 'amd',
compact: false,
presets: ['es2015']
},
esdoc: {
command: 'npm run esdoc'
dist: {
files: [{
expand: true,
cwd: 'src',
src: ['**/*.js'],
dest: 'dist/tablefilter'
}]
}
},
esdoc: {
dist: {
options: {
source: 'src',
destination: 'docs/docs',
title: pkg.name + ' v' + pkg.version
}
}
},
stylus: {
compile: {
options: {
banner: '/** \n' +
' *\t '+ pkg.name +' v'+ pkg.version +
' by '+ pkg.author.name +' \n' +
' *\t build date: '+ new Date().toISOString() +' \n' +
' *\t MIT License \n' +
' */ \n'
},
files: [
{
src: ['static/style/*.styl'],
dest: 'dist/tablefilter/style/tablefilter.css'
},{
src: ['static/style/extensions/colsVisibility.styl'],
dest: 'dist/tablefilter/style/colsVisibility.css'
},{
src: ['static/style/extensions/filtersVisibility.styl'],
dest: 'dist/tablefilter/style/filtersVisibility.css'
},{
src: ['static/style/themes/default/*.styl'],
dest:
'dist/tablefilter/style/themes/default/default.css'
},{
src: ['static/style/themes/mytheme/*.styl'],
dest:
'dist/tablefilter/style/themes/mytheme/mytheme.css'
},{
src: ['static/style/themes/skyblue/*.styl'],
dest:
'dist/tablefilter/style/themes/skyblue/skyblue.css'
}
]
}
},
'gh-pages': {
options: {
branch: 'gh-pages',
add: true
},
build: {
command: 'npm run build'
'publish-lib': {
options: {
base: 'dist',
repo: 'https://' + repo,
message: 'publish TableFilter lib to gh-pages (cli)'
},
src: ['**/*']
},
dev: {
command: 'npm run dev'
'publish-readme': {
options: {
base: './',
repo: 'https://' + repo,
message: 'publish README and LICENSE to gh-pages (cli)'
},
src: ['README.md', 'LICENSE']
},
test: {
command: 'npm run build:test'
'publish-docs': {
options: {
base: 'docs',
repo: 'https://' + repo,
message: 'publish Docs to gh-pages (cli)'
},
src: ['**/*']
},
'build-css': {
command: 'npm run build:css'
'deploy-lib': {
options: {
user: {
name: 'koalyptus'
},
base: 'dist',
repo: 'https://' + process.env.GH_TOKEN + '@' + repo,
message: 'publish TableFilter to gh-pages (auto)' +
getDeployMessage(),
silent: true
},
src: ['**/*']
},
'deploy-readme': {
options: {
user: {
name: 'koalyptus'
},
base: './',
repo: 'https://' + process.env.GH_TOKEN + '@' + repo,
message: 'publish README to gh-pages (auto)' +
getDeployMessage(),
silent: true
},
src: ['README.md', 'LICENSE']
},
'deploy-docs': {
options: {
user: {
name: 'koalyptus'
},
base: 'docs',
repo: 'https://' + process.env.GH_TOKEN + '@' + repo,
message: 'publish Docs to gh-pages (auto)' +
getDeployMessage(),
silent: true
},
src: ['**/*']
}
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-string-replace');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-shell');
grunt.loadNpmTasks('grunt-qunit-istanbul');
grunt.loadNpmTasks('grunt-webpack');
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-esdoc');
grunt.loadNpmTasks('grunt-contrib-stylus');
grunt.loadNpmTasks('grunt-gh-pages');
grunt.registerTask('eslint', ['shell:eslint']);
grunt.registerTask('esdoc', ['shell:esdoc']);
grunt.registerTask('default', ['build', 'test', 'build-demos']);
grunt.registerTask('default', ['test', 'build', 'build-demos']);
// Development server
grunt.registerTask('server', ['webpack-dev-server:start']);
// Dev dev/build/watch cycle
grunt.registerTask('dev',
['eslint', 'shell:dev', 'copy:dist', 'shell:build-css', 'watch:app']);
['jshint', 'webpack:dev', 'copy:dist', 'stylus:compile', 'watch:app']);
// Production build
grunt.registerTask('build',
['eslint', 'shell:build', 'copy:dist', 'shell:build-css']);
['jshint', 'webpack:build', 'copy:dist', 'stylus:compile']);
// Build demos
grunt.registerTask('dev-demos', ['build-demos', 'watch:templates']);
grunt.registerTask('build-demos', ['copy:templates', 'copy:assets',
'string-replace:demos', 'copy:starter', 'clean']);
// Build tests
grunt.registerTask('build-test',
['eslint', 'shell:test', 'copy:dist', 'shell:build-css']);
// Transpile with Babel
grunt.registerTask('dev-modules', ['babel', 'copy:dist']);
// Tests with coverage
grunt.registerTask('test', ['build-test', 'connect', 'qunit:all']);
// Tests
grunt.registerTask('test', ['jshint', 'connect', 'qunit:all']);
// Build all for deployment from travis
grunt.registerTask('build-all', 'Prepare for deployment', [
'build', 'build-demos', 'esdoc'
// Publish to gh-pages
grunt.registerTask('publish', 'Publish from CLI', [
'build', 'build-demos', 'esdoc', 'gh-pages:publish-lib',
'gh-pages:publish-readme', 'gh-pages:publish-docs'
]);
// Deploy to gh-pages
grunt.registerTask('deploy', 'Publish from Travis', [
'build', 'esdoc', 'check-deploy'
]);
// Custom task running QUnit tests for specified files.
// Usage example: grunt test-only:test.html,test-help.html
grunt.registerTask('test-only',
'A task that runs only specified tests.',
function (tests) {
if (!tests) {
function(tests) {
if(!tests) {
return;
}
tests = tests.split(',');
var res = [];
tests.forEach(function (itm) {
tests.forEach(function(itm) {
var filePath = path.resolve(testDir, itm);
var parts = filePath.split(path.sep);
res.push(buildTestUrl(testHost, testDir, parts));
@ -205,14 +350,13 @@ module.exports = function (grunt) {
grunt.task.run('connect');
grunt.config('qunit.only.options.urls', res);
grunt.task.run('qunit:only');
}
);
});
function isTestFile(pth) {
var allowedExts = ['.html', '.htm'];
for (var i = 0, len = allowedExts.length; i < len; i++) {
for(var i=0, len=allowedExts.length; i<len; i++){
var ext = allowedExts[i];
if (pth.indexOf(ext) !== -1) {
if(pth.indexOf(ext) !== -1){
return true;
}
}
@ -228,17 +372,18 @@ module.exports = function (grunt) {
// Returns the list of test files from the test folder for qunit task
function getTestFiles(testDir, host) {
var getFiles = function (dir, host) {
var getFiles = function(dir, host) {
var res = [];
var items = fs.readdirSync(dir);
items.forEach(function (itm) {
items.forEach(function(itm){
var fileOrDir = path.resolve(dir, itm);
if (isTestFile(fileOrDir)) {
if(isTestFile(fileOrDir)) {
var parts = fileOrDir.split(path.sep);
res.push(buildTestUrl(host, testDir, parts));
} else {
if (fs.lstatSync(fileOrDir).isDirectory()) {
if(fs.lstatSync(fileOrDir).isDirectory()) {
res = res.concat(getFiles(fileOrDir, host));
}
}
@ -249,4 +394,43 @@ module.exports = function (grunt) {
return getFiles(testDir, host);
}
grunt.registerTask('check-deploy', function() {
var env = process.env;
// need this
this.requires(['build', 'esdoc']);
// only deploy under these conditions
if (env.TRAVIS === 'true' &&
env.TRAVIS_SECURE_ENV_VARS === 'true' &&
env.TRAVIS_PULL_REQUEST === 'false') {
grunt.log.writeln('executing deployment');
// queue deploy
grunt.task.run([
'gh-pages:deploy-lib',
'gh-pages:deploy-readme',
'gh-pages:deploy-docs'
]);
} else {
grunt.log.writeln('skipped deployment');
}
});
// Get a formatted commit message to review changes from the commit log
// github will turn some of these into clickable links
function getDeployMessage() {
var ret = '\n\n';
var env = process.env;
if (env.TRAVIS !== 'true') {
ret += 'missing env vars for travis-ci';
return ret;
}
ret += 'branch: ' + env.TRAVIS_BRANCH + '\n';
ret += 'SHA: ' + env.TRAVIS_COMMIT + '\n';
ret += 'range SHA: ' + env.TRAVIS_COMMIT_RANGE + '\n';
ret += 'build id: ' + env.TRAVIS_BUILD_ID + '\n';
ret += 'build number: ' + env.TRAVIS_BUILD_NUMBER + '\n';
return ret;
}
};

View file

@ -1,24 +0,0 @@
# Description
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
- [ ] Development tool configuration change
# Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes do not lower current test coverage
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes

116
README.md
View file

@ -1,10 +1,5 @@
[![Build Status](https://api.travis-ci.org/koalyptus/TableFilter.svg?branch=master)](https://travis-ci.org/koalyptus/TableFilter)
[![Document](https://www.tablefilter.com/docs/badge.svg)](https://www.tablefilter.com/docs/source.html)
[![codecov](https://codecov.io/gh/koalyptus/TableFilter/branch/master/graph/badge.svg)](https://codecov.io/gh/koalyptus/TableFilter)
[![Greenkeeper badge](https://badges.greenkeeper.io/koalyptus/TableFilter.svg)](https://greenkeeper.io/)
[![Donate](https://img.shields.io/badge/donate-%24-green.svg)](https://www.tablefilter.com/donate.html)
# TableFilter
TableFilter [![Build Status](https://api.travis-ci.org/koalyptus/TableFilter.svg?branch=master)](https://travis-ci.org/koalyptus/TableFilter)
===========================
> A Javascript library making HTML tables filterable
@ -15,59 +10,40 @@ users to filter and limit the data displayed within a long table. By default, th
## Features
* Convert a regular HTML table into an advanced grid component providing:
* Advanced columns filtering model
* Sorting and pagination capabilities
* Sorting and pagination facilities
* Complete selection model ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus) extension)
* Extended keyboard navigation ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus) extension)
* Inline cell or row editing ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus) extension)
* Row insertion or deleting ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus) extension)
* And even more features...
* And even more behaviors...
* Attach to an existing HTML table
* Integration with any server-side technology as this is a pure client-side
solution
* Exhaustive documentation and powerful API
* Callbacks for all events, and delegates for most actions
* Exhaustive documentation and API
## Getting started
* Clone the repo using Git:
```shell
git clone https://github.com/koalyptus/TableFilter.git
git clone --bare https://github.com/koalyptus/TableFilter.git
```
* You can [download](https://github.com/koalyptus/TableFilter/archive/master.zip) this repository.
* TableFilter is available on [npm repository](https://www.npmjs.com/package/tablefilter), you can install it from the command line using the following command:
* Alternatively, install TableFilter files in your npm enabled project using:
```shell
npm install tablefilter --save-dev
```
npm install tablefilter --save
```
* or get the future features from the ``next`` release channel:
```shell
npm install tablefilter@next --save-dev
npm install tablefilter@next --save
```
* Alternatively you can also [access these files from unpkg CDN](https://unpkg.com/tablefilter/), download them or point your package manager to them.
## Setup
### Using modules
Require `TableFilter`:
```javascript
// ES2015 modules
import TableFilter from 'tablefilter';
// CommonJS or AMD modules
var TableFilter = require('tablefilter');
```
### Using distribution scripts
If you are not using a module system, you can reference the distribution scripts directly in your html pages:
```html
<script src="path_to/node_modules/tablefilter/dist/tablefilter/tablefilter.js"></script>
```
### Placing manually the distribution scripts in your project
Copy the ``tablefilter`` directory under ``dist`` and place it at desired location in your project. Then include the main js file in your page:
```shell
<script src="path/to/my/scripts/tablefilter/tablefilter.js"></script>
```
### Usage
Place the following snippet just under the HTML table and always define a ``base_path`` property in the configuration object to reflect the path to the script
```shell
<script>
@ -81,11 +57,11 @@ If the ``base_path`` property is not specified, it will default to ``/tablefilte
```shell
your-page.html
|— tablefilter
```
```
## Development
This project requires node.js and Grunt to be installed:
- install [node.js](https://nodejs.org/) v8.9.4 or higher
- install [node.js](https://nodejs.org/)
- install [Grunt](http://gruntjs.com/getting-started) from the command line using npm (comes with node.js):
```shell
npm install -g grunt-cli
@ -96,52 +72,46 @@ Start by installing any dependencies.
```shell
npm install
```
Use
```shell
npm run dev
```
command to launch a build / watch cycle and start the local
sever on port ``8080``.
Use
```shell
npm run build
```
command to generate a production build.
The
```shell
npm run dist
```
command will create a production build, run the tests and finally generate
the demos:
To run all the tests and generate the coverage report:
Use the Grunt ``dev`` task to launch a build / watch cycle and start the local
sever on port ``8080``:
```shell
npm test
grunt dev
```
or to run specific test(s):
Use the ``build`` task to generate a production build:
```shell
grunt build
```
The ``default`` Grunt task will create a production build, run the tests and finally generate the demos:
```shell
grunt
```
To run all the tests:
```shell
grunt test
```
and to run specific test(s):
```shell
grunt test-only:test.html
grunt test-only:test.html,test-sort.html
```
to view the coverage report(s), open the `index.html` under the
`report/coverage` folder or
[online](https://codecov.io/gh/koalyptus/TableFilter).
## Demos
Check out the online [examples](http://www.tablefilter.com/examples.html)
or generate the demos locally:
Check out the online [examples](http://koalyptus.github.io/TableFilter/examples) or generate the demos locally:
```shell
npm run build:demos
grunt build-demos
```
then run the local webserver:
```shell
npm start
grunt server
```
then pick a demo from:
```shell
@ -151,18 +121,16 @@ http://localhost:8080/demos/
## Documentation
Find exhaustive documentation on the configuration options in the [WIKI](https://github.com/koalyptus/TableFilter/wiki) section.
Autogenerated documentation of the ES6 modules is available on the website: [docs](http://www.tablefilter.com/docs)
If you previously used the HTML Table Filter Generator plugin, verify the configuration
options you are using are still supported: [Obsolete](https://github.com/koalyptus/TableFilter/wiki/Obsolete)
Autogenerated documentation of the ES6 modules is available on the website: [docs](http://koalyptus.github.io/TableFilter/docs)
Run this task to generate the documentation in the ``docs/docs`` directory:
```shell
npm run esdoc
grunt esdoc
```
## Support
* GitHub for [reporting bugs](https://github.com/koalyptus/TableFilter/blob/master/CONTRIBUTING.md#reporting-bugs) and [feature requests](https://github.com/koalyptus/TableFilter/blob/master/CONTRIBUTING.md#suggesting-enhancements-and-features).
* GitHub for reporting bugs and feature requests.
## License
[MIT](LICENSE)

14
dist/starter.html vendored
View file

@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>tablefilter v0.7.3 - Starter</title>
<title>tablefilter v0.1.14 - Starter</title>
</head>
<body>
<h1>tablefilter v0.7.3</h1>
<h1>tablefilter v0.1.14</h1>
@ -460,10 +460,10 @@
status_bar: true,
mark_active_columns: true,
highlight_keywords: true,
col_types: [
'string', 'string', 'number',
'number', 'number', 'number',
'number', 'number', 'number'
col_number_format: [
null, null, 'US',
'US', 'US', 'US',
'US', 'US', 'US'
],
custom_options: {
cols:[3],
@ -479,7 +479,7 @@
},
col_widths: [
'150px', '100px', '100px',
'100px', '100px', '100px',
'70px', '70px', '70px',
'70px', '60px', '60px'
],
extensions:[{ name: 'sort' }]

View file

@ -1 +1,6 @@
span.colVisSpan{text-align:left;}span.colVisSpan a.colVis{display:inline-block;padding:7px 5px 0;font-size:inherit;font-weight:inherit;vertical-align:top}div.colVisCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;border:1px solid #ccc;height:auto;width:250px;background-color:#fff;margin:35px 0 0 -100px;z-index:10000;padding:10px 10px 10px 10px;text-align:left;font-size:inherit;}div.colVisCont:after,div.colVisCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.colVisCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.colVisCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.colVisCont p{margin:6px auto 6px auto}div.colVisCont a.colVis{display:initial;font-weight:inherit}ul.cols_checklist{padding:0;margin:0;list-style-type:none;}ul.cols_checklist label{display:block}ul.cols_checklist input{vertical-align:middle;margin:2px 5px 2px 1px}li.cols_checklist_item{padding:4px;margin:0;}li.cols_checklist_item:hover{background-color:#335ea8;color:#fff}.cols_checklist_slc_item{background-color:#335ea8;color:#fff}
/**
* tablefilter v0.1.14 by Max Guglielmi
* build date: 2016-02-13T12:24:12.171Z
* MIT License
*/
span.colVisSpan{text-align:left;}span.colVisSpan a.colVis{display:inline-block;padding:7px 5px 0;font-size:inherit;font-weight:inherit;vertical-align:top}div.colVisCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;border:1px solid #ccc;height:auto;width:250px;background-color:#fff;margin:35px 0 0 -100px;z-index:10000;padding:10px 10px 10px 10px;text-align:left;font-size:12px;}div.colVisCont:after,div.colVisCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.colVisCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.colVisCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.colVisCont p{margin:6px auto 6px auto}div.colVisCont a.colVis{display:initial;font-weight:inherit}ul.cols_checklist{padding:0;margin:0;list-style:none;}ul.cols_checklist label{display:block}ul.cols_checklist input{vertical-align:middle;margin:2px 5px 2px 1px}li.cols_checklist_item{padding:4px;margin:0;}li.cols_checklist_item:hover{background-color:#335ea8;color:#fff}.cols_checklist_slc_item{background-color:#335ea8;color:#fff}

View file

@ -1 +1,6 @@
/**
* tablefilter v0.1.14 by Max Guglielmi
* build date: 2016-02-13T12:24:12.171Z
* MIT License
*/
span.expClpFlt a.btnExpClpFlt{width:35px;height:35px;display:inline-block;}span.expClpFlt a.btnExpClpFlt:hover{background-color:#f4f4f4}span.expClpFlt img{padding:8px 11px 11px 11px}

File diff suppressed because one or more lines are too long

View file

@ -1 +1,6 @@
/**
* tablefilter v0.1.14 by Max Guglielmi
* build date: 2016-02-13T12:24:12.171Z
* MIT License
*/
table.TF{border-left:1px solid #ccc;border-top:none;border-right:none;border-bottom:none;}table.TF th{background:#ebecee url("images/bg_th.jpg") left top repeat-x;border-bottom:1px solid #d0d0d0;border-right:1px solid #d0d0d0;border-left:1px solid #fff;border-top:1px solid #fff;color:#333}table.TF td{border-bottom:1px dotted #999;padding:5px}.fltrow{background-color:#ebecee !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #666 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #999 !important}input.flt{width:99% !important}.inf{height:$min-height;background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important}input.reset{background:transparent url("images/btn_eraser.gif") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background:transparent url("images/btn_over_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background:transparent url("images/btn_over_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background:transparent url("images/btn_over_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background:transparent url("images/btn_over_last_page.gif") center center no-repeat !important}div.grd_Cont{background-color:#ebecee !important;border:1px solid #ccc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#d5d5d5}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important;}div.grd_headTblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#ebecee url("images/bg_th.jpg") left top repeat-x !important;border-bottom:1px solid #d0d0d0 !important;border-right:1px solid #d0d0d0 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #999 !important}.grd_inf{background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important;border-top:1px solid #d0d0d0 !important}.loader{border:1px solid #999}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#fff}.odd{background-color:#d5d5d5}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#999 !important}

View file

@ -1 +1,6 @@
/**
* tablefilter v0.1.14 by Max Guglielmi
* build date: 2016-02-13T12:24:12.171Z
* MIT License
*/
table.TF{border-left:1px dotted #81963b;border-top:none;border-right:0;border-bottom:none;}table.TF th{background:#39424b url("images/bg_headers.jpg") left top repeat-x;border-bottom:0;border-right:1px dotted #d0d0d0;border-left:0;border-top:0;color:#fff}table.TF td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b;padding:5px}.fltrow{background-color:#81963b !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #687830 !important}input.flt{width:99% !important}.inf{background:#d8d8d8;height:$min-height}input.reset{width:53px;background:transparent url("images/btn_filter.png") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important}div.grd_Cont{background:#81963b url("images/bg_headers.jpg") left top repeat-x !important;border:1px solid #ccc !important;padding:0 1px 1px 1px !important;}div.grd_Cont .even{background-color:#bccd83}div.grd_Cont .odd{background-color:#fff}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important;}div.grd_tblCont table td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b}div.grd_tblCont table th,div.grd_headTblCont table th{background:transparent url("images/bg_headers.jpg") 0 0 repeat-x !important;border-bottom:0 !important;border-right:1px dotted #d0d0d0 !important;border-left:0 !important;border-top:0 !important;padding:0 4px 0 4px !important;color:#fff !important;height:35px !important}div.grd_headTblCont table td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;background-color:#81963b !important;padding:1px 3px 1px 3px !important}.grd_inf{background-color:#d8d8d8;border-top:1px solid #d0d0d0 !important}.loader{border:0 !important;background:#81963b !important}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#bccd83}.odd{background-color:#fff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#81963b !important}

View file

@ -1 +1,6 @@
/**
* tablefilter v0.1.14 by Max Guglielmi
* build date: 2016-02-13T12:24:12.171Z
* MIT License
*/
table.TF{padding:0;color:#000;border-right:1px solid #a4bed4;border-top:1px solid #a4bed4;border-left:1px solid #a4bed4;border-bottom:0;}table.TF th{margin:0;color:inherit;background:#d1e5fe url("images/bg_skyblue.gif") 0 0 repeat-x;border-color:#fdfdfd #a4bed4 #a4bed4 #fdfdfd;border-width:1px;border-style:solid}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid #a4bed4;border-left:0;border-top:0;border-right:0}.fltrow{background-color:#d1e5fe !important;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4 !important}input.flt{width:99% !important}.inf{background-color:#e3efff !important;border:1px solid #a4bed4;height:$min-height;color:#004a6f}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#ffe4ab url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#ffe4ab url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#ffe4ab url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#ffe4ab url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.activeHeader{background:#ffe4ab !important;border:1px solid #ffb552 !important;color:inherit !important}div.grd_Cont{background-color:#d9eaed !important;border:1px solid #9cc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#e3efff}div.grd_headTblCont{background-color:#d9eaed !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#d9eaed url("images/bg_skyblue.gif") left top repeat-x;border-bottom:1px solid #a4bed4;border-right:1px solid #a4bed4 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #a4bed4 !important;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:#cce2fe;color:#004a6f;border-top:1px solid #9cc !important;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#2d8eef;border:1px solid #cce2fe;border-radius:5px}.even{background-color:#fff}.odd{background-color:#e3efff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ffdc61 !important;color:inherit}.ezSelectedRow{background-color:#ffe4ab !important;color:inherit}.ezActiveCell{background-color:#fff !important;color:#000 !important;font-weight:bold}.ezETSelectedCell{background-color:#fff !important;font-weight:bold;color:#000 !important}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 B

View file

@ -1 +0,0 @@
table.TF{padding:0;color:inherit;border-right:1px solid transparent;border-top:1px solid transparent;border-left:1px solid transparent;border-bottom:0;}table.TF th{margin:0;color:inherit;background-color:transparent;border-color:transparent;border-width:1px;border-style:solid;}table.TF th:last-child{border-right:1px solid transparent}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid transparent;border-left:0;border-top:0;border-right:0}.fltrow{background-color:transparent;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px;border-bottom:1px solid transparent !important;}.fltrow th:last-child,.fltrow td:last-child{border-right:1px solid transparent}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4}input.flt{width:99% !important}.inf{background-color:transparent;border:1px solid transparent;height:$min-height;color:inherit}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#f7f7f7 url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#f7f7f7 url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#f7f7f7 url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#f7f7f7 url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.activeHeader{background:#f7f7f7 !important;border:1px solid transparent;color:inherit !important}div.grd_Cont{-webkit-box-shadow:0 0 0 0 rgba(50,50,50,0.75);-moz-box-shadow:0 0 0 0 rgba(50,50,50,0.75);box-shadow:0 0 0 0 rgba(50,50,50,0.75);background-color:transparent;border:1px solid transparent;padding:0 !important;}div.grd_Cont .even{background-color:transparent}div.grd_Cont .odd{background-color:#f7f7f7}div.grd_headTblCont{background-color:transparent;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:transparent;border-bottom:1px solid transparent;border-right:1px solid transparent !important;border-left:1px solid transparent;border-top:1px solid transparent}div.grd_tblCont table td{border-bottom:1px solid transparent;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:transparent;color:inherit;border-top:1px solid transparent;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#f7f7f7;border:1px solid #f7f7f7;border-radius:5px;color:#000;text-shadow:none}.even{background-color:transparent}.odd{background-color:#f7f7f7}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ccc !important;color:inherit}.ezSelectedRow{background-color:#ccc !important;color:inherit}.ezActiveCell{background-color:transparent;color:inherit;font-weight:bold}.ezETSelectedCell{background-color:transparent;font-weight:bold;color:inherit}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

8
dist/tablefilter/tf-1.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,3 +0,0 @@
'use strict';
module.exports = require('./dist/tablefilter/tablefilter').TableFilter;

12319
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "tablefilter",
"version": "0.7.3",
"version": "0.1.14",
"description": "A Javascript library making HTML tables filterable and a bit more",
"license": "MIT",
"author": {
@ -22,58 +22,37 @@
"pagination"
],
"scripts": {
"lint": "eslint src/**/*.js test/*.js *.js",
"dev": "webpack --colors --config webpack.dev.config.js",
"build": "webpack --colors --config webpack.config.js",
"build:css": "stylus -c static/style/tablefilter.styl -o dist/tablefilter/style && stylus -c static/style/extensions -o dist/tablefilter/style && stylus -c static/style/themes/default -o dist/tablefilter/style/themes/default && stylus -c static/style/themes/mytheme -o dist/tablefilter/style/themes/mytheme && stylus -c static/style/themes/skyblue -o dist/tablefilter/style/themes/skyblue && stylus -c static/style/themes/transparent -o dist/tablefilter/style/themes/transparent",
"build:demos": "grunt build-demos",
"build:test": "webpack --colors --config webpack.test.config.js",
"server": "webpack-dev-server --colors --hot --config ./webpack.dev.config.js",
"test": "grunt test",
"codecov": "./node_modules/.bin/codecov",
"esdoc": "esdoc",
"dist": "grunt",
"build:all": "grunt build-all",
"start": "npm run server"
"test": "grunt test"
},
"publishConfig": {
"tag": "next"
},
"devDependencies": {
"@babel/core": "7.10.0",
"@babel/preset-env": "7.10.0",
"babel-eslint": "10.1.0",
"babel-loader": "^8.0.2",
"babel-preset-env": "1.7.0",
"clean-webpack-plugin": "^3.0.0",
"codecov": "3.7.1",
"diacritics": "1.3.0",
"esdoc": "1.1.0",
"esdoc-standard-plugin": "1.0.0",
"eslint": "6.5.0",
"format-number": "3.0.0",
"grunt": "^1.0.1",
"grunt-cli": "1.3.2",
"grunt-contrib-clean": "^2.0.0",
"grunt-contrib-connect": "^2.0.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-qunit-istanbul": "1.1.0",
"grunt-shell": "3.0.0",
"grunt-string-replace": "^1.3.1",
"isparta-loader": "2.0.0",
"script-loader": "^0.7.0",
"string-replace-webpack-plugin": "^0.1.3",
"stylus": "^0.54.5",
"sugar-date": "2.0.6",
"uglifyjs-webpack-plugin": "2.2.0",
"webpack": "^4.38.0",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.11"
"babel-core": "^6.1.2",
"babel-loader": "^6.0.1",
"babel-preset-es2015": "^6.3.13",
"clean-webpack-plugin": "^0.1.3",
"grunt": "^0.4.5",
"grunt-babel": "^6.0.0",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-connect": "^0.10.1",
"grunt-contrib-copy": "^0.8.0",
"grunt-contrib-jshint": "^0.11.2",
"grunt-contrib-qunit": "^0.7.0",
"grunt-contrib-stylus": "^0.22.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-esdoc": "^0.0.1",
"grunt-gh-pages": "^0.10.0",
"grunt-string-replace": "^1.2.0",
"grunt-webpack": "^1.0.8",
"script-loader": "^0.6.1",
"string-replace-webpack-plugin": "^0.0.1",
"webpack": "^1.8.10",
"webpack-dev-server": "^1.8.2"
},
"dependencies": {},
"bugs": {
"url": "https://github.com/koalyptus/TableFilter/issues"
},
"homepage": "https://www.tablefilter.com"
"homepage": "http://koalyptus.github.io/TableFilter"
}

View file

@ -2,21 +2,16 @@
* Array utilities
*/
import {matchCase} from './string';
import Str from './string';
/**
* Checks if given item can be found in the passed collection
* @param {Array} arr collection
* @param {Any} val item to search
* @param {Boolean} caseSensitive respects case if true
* @return {Boolean}
*/
export const has = (arr, val, caseSensitive) => {
let sCase = Boolean(caseSensitive);
for (var i = 0, l = arr.length; i < l; i++) {
if (matchCase(arr[i].toString(), sCase) === val) {
return true;
export default {
has: function(arr, val, caseSensitive){
let sCase = caseSensitive===undefined ? false : caseSensitive;
for (var i=0; i<arr.length; i++){
if(Str.matchCase(arr[i].toString(), sCase) == val){
return true;
}
}
return false;
}
return false;
};

View file

@ -1,118 +0,0 @@
/**
* Filter types
*/
/**
* Input filter type
* @type {String}
*/
export const INPUT = 'input';
/**
* Select filter type
* @type {String}
*/
export const SELECT = 'select';
/**
* Multiple select filter type
* @type {String}
*/
export const MULTIPLE = 'multiple';
/**
* Checklist filter type
* @type {String}
*/
export const CHECKLIST = 'checklist';
/**
* None filter type
* @type {String}
*/
export const NONE = 'none';
/**
* Key codes
*/
/**
* Enter key code
* @type {Number}
*/
export const ENTER_KEY = 13;
/**
* Tab key code
* @type {Number}
*/
export const TAB_KEY = 9;
/**
* Escape key code
* @type {Number}
*/
export const ESC_KEY = 27;
/**
* Up arrow key code
* @type {Number}
*/
export const UP_ARROW_KEY = 38;
/**
* Down arrow key code
* @type {Number}
*/
export const DOWN_ARROW_KEY = 40;
/**
* HTML tags
*/
/**
* Header cell tag
* @type {String}
*/
export const HEADER_TAG = 'TH';
/**
* Cell tag
* @type {String}
*/
export const CELL_TAG = 'TD';
/**
* Data types
*/
/**
* String
* @type {String}
*/
export const STRING = 'string';
/**
* Number
* @type {String}
*/
export const NUMBER = 'number';
/**
* Formatted number
* @type {String}
*/
export const FORMATTED_NUMBER = 'formatted-number';
/**
* Date
* @type {String}
*/
export const DATE = 'date';
/**
* IP address
* @type {String}
*/
export const IP_ADDRESS = 'ipaddress';
/**
* Default values
*/
/**
* Auto filter delay in milliseconds
* @type {Number}
*/
export const AUTO_FILTER_DELAY = 750;

View file

@ -1,43 +1,28 @@
import {root} from './root';
/**
* Cookie utilities
*/
const doc = root.document;
export default {
/**
* Write a cookie
* @param {String} name Name of the cookie
* @param {String} value Value of the cookie
* @param {Number} hours Cookie duration in hours
*/
write(name, value, hours) {
write(name, value, hours){
let expire = '';
if (hours) {
if(hours){
expire = new Date((new Date()).getTime() + hours * 3600000);
expire = '; expires=' + expire.toGMTString();
}
doc.cookie = name + '=' + escape(value) + expire;
document.cookie = name + '=' + escape(value) + expire;
},
/**
* Read a cookie
* @param {String} name Name of the cookie
* @returns {String} Value of the cookie
*/
read(name) {
read(name){
let cookieValue = '',
search = name + '=';
if (doc.cookie.length > 0) {
let cookie = doc.cookie,
if(document.cookie.length > 0){
let cookie = document.cookie,
offset = cookie.indexOf(search);
if (offset !== -1) {
if(offset !== -1){
offset += search.length;
let end = cookie.indexOf(';', offset);
if (end === -1) {
if(end === -1){
end = cookie.length;
}
cookieValue = unescape(cookie.substring(offset, end));
@ -46,12 +31,28 @@ export default {
return cookieValue;
},
/**
* Remove a cookie
* @param {String} name Name of the cookie
*/
remove(name) {
remove(name){
this.write(name, '', -1);
},
valueToArray(name, separator){
if(!separator){
separator = ',';
}
//reads the cookie
let val = this.read(name);
//creates an array with filters' values
let arr = val.split(separator);
return arr;
},
getValueByIndex(name, index, separator){
if(!separator){
separator = ',';
}
//reads the cookie
let val = this.valueToArray(name, separator);
return val[index];
}
};

172
src/date.js Normal file
View file

@ -0,0 +1,172 @@
/**
* Date utilities
*/
export default {
isValid(dateStr, format){
if(!format) {
format = 'DMY';
}
format = format.toUpperCase();
if(format.length != 3) {
if(format === 'DDMMMYYYY'){
let d = this.format(dateStr, format);
dateStr = d.getDate() +'/'+ (d.getMonth()+1) +'/'+
d.getFullYear();
format = 'DMY';
}
}
if((format.indexOf('M') === -1) || (format.indexOf('D') === -1) ||
(format.indexOf('Y') === -1)){
format = 'DMY';
}
let reg1, reg2;
// If the year is first
if(format.substring(0, 1) === 'Y') {
reg1 = /^\d{2}(\-|\/|\.)\d{1,2}\1\d{1,2}$/;
reg2 = /^\d{4}(\-|\/|\.)\d{1,2}\1\d{1,2}$/;
} else if(format.substring(1, 2) === 'Y') { // If the year is second
reg1 = /^\d{1,2}(\-|\/|\.)\d{2}\1\d{1,2}$/;
reg2 = /^\d{1,2}(\-|\/|\.)\d{4}\1\d{1,2}$/;
} else { // The year must be third
reg1 = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{2}$/;
reg2 = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{4}$/;
}
// If it doesn't conform to the right format (with either a 2 digit year
// or 4 digit year), fail
if(reg1.test(dateStr) === false && reg2.test(dateStr) === false) {
return false;
}
// Split into 3 parts based on what the divider was
let parts = dateStr.split(RegExp.$1);
let mm, dd, yy;
// Check to see if the 3 parts end up making a valid date
if(format.substring(0, 1) === 'M'){
mm = parts[0];
} else if(format.substring(1, 2) === 'M'){
mm = parts[1];
} else {
mm = parts[2];
}
if(format.substring(0, 1) === 'D'){
dd = parts[0];
} else if(format.substring(1, 2) === 'D'){
dd = parts[1];
} else {
dd = parts[2];
}
if(format.substring(0, 1) === 'Y'){
yy = parts[0];
} else if(format.substring(1, 2) === 'Y'){
yy = parts[1];
} else {
yy = parts[2];
}
if(parseInt(yy, 10) <= 50){
yy = (parseInt(yy, 10) + 2000).toString();
}
if(parseInt(yy, 10) <= 99){
yy = (parseInt(yy, 10) + 1900).toString();
}
let dt = new Date(
parseInt(yy, 10), parseInt(mm, 10)-1, parseInt(dd, 10),
0, 0, 0, 0);
if(parseInt(dd, 10) != dt.getDate()){
return false;
}
if(parseInt(mm, 10)-1 != dt.getMonth()){
return false;
}
return true;
},
format(dateStr, formatStr) {
if(!formatStr){
formatStr = 'DMY';
}
if(!dateStr || dateStr === ''){
return new Date(1001, 0, 1);
}
let oDate;
let parts;
switch(formatStr.toUpperCase()){
case 'DDMMMYYYY':
parts = dateStr.replace(/[- \/.]/g,' ').split(' ');
oDate = new Date(y2kDate(parts[2]),mmm2mm(parts[1])-1,parts[0]);
break;
case 'DMY':
/* jshint ignore:start */
parts = dateStr.replace(
/^(0?[1-9]|[12][0-9]|3[01])([- \/.])(0?[1-9]|1[012])([- \/.])((\d\d)?\d\d)$/,'$1 $3 $5').split(' ');
oDate = new Date(y2kDate(parts[2]),parts[1]-1,parts[0]);
/* jshint ignore:end */
break;
case 'MDY':
/* jshint ignore:start */
parts = dateStr.replace(
/^(0?[1-9]|1[012])([- \/.])(0?[1-9]|[12][0-9]|3[01])([- \/.])((\d\d)?\d\d)$/,'$1 $3 $5').split(' ');
oDate = new Date(y2kDate(parts[2]),parts[0]-1,parts[1]);
/* jshint ignore:end */
break;
case 'YMD':
/* jshint ignore:start */
parts = dateStr.replace(/^((\d\d)?\d\d)([- \/.])(0?[1-9]|1[012])([- \/.])(0?[1-9]|[12][0-9]|3[01])$/,'$1 $4 $6').split(' ');
oDate = new Date(y2kDate(parts[0]),parts[1]-1,parts[2]);
/* jshint ignore:end */
break;
default: //in case format is not correct
/* jshint ignore:start */
parts = dateStr.replace(/^(0?[1-9]|[12][0-9]|3[01])([- \/.])(0?[1-9]|1[012])([- \/.])((\d\d)?\d\d)$/,'$1 $3 $5').split(' ');
oDate = new Date(y2kDate(parts[2]),parts[1]-1,parts[0]);
/* jshint ignore:end */
break;
}
return oDate;
}
};
function y2kDate(yr){
if(yr === undefined){
return 0;
}
if(yr.length>2){
return yr;
}
let y;
//>50 belong to 1900
if(yr <= 99 && yr>50){
y = '19' + yr;
}
//<50 belong to 2000
if(yr<50 || yr === '00'){
y = '20' + yr;
}
return y;
}
function mmm2mm(mmm){
if(mmm === undefined){
return 0;
}
let mondigit;
let MONTH_NAMES = [
'january','february','march','april','may','june','july',
'august','september','october','november','december',
'jan','feb','mar','apr','may','jun','jul','aug','sep','oct',
'nov','dec'
];
for(let m_i=0; m_i < MONTH_NAMES.length; m_i++){
let month_name = MONTH_NAMES[m_i];
if (mmm.toLowerCase() === month_name){
mondigit = m_i+1;
break;
}
}
if(mondigit > 11 || mondigit < 23){
mondigit = mondigit - 12;
}
if(mondigit < 1 || mondigit > 12){
return 0;
}
return mondigit;
}

View file

@ -1,192 +1,165 @@
import {root} from './root';
import {isArray, isString, isUndef} from './types';
import {trim} from './string';
/**
* DOM utilities
*/
const doc = root.document;
export default {
/**
* Returns text + text of children of given node
* @param {NodeElement} node
* @return {String}
*/
export const getText = (node) => {
if (isUndef(node.textContent)) {
return trim(node.innerText);
}
return trim(node.textContent);
};
/**
* Returns text + text of children of given node
* @param {NodeElement} node
* @return {String}
*/
getText(node){
let s = node.textContent || node.innerText ||
node.innerHTML.replace(/<[^<>]+>/g, '');
s = s.replace(/^\s+/, '').replace(/\s+$/, '');
return s;
},
/**
* Returns the first text node contained in the supplied node
* @param {NodeElement} node node
* @return {String}
*/
export const getFirstTextNode = (node) => {
for (let i = 0; i < node.childNodes.length; i++) {
let n = node.childNodes[i];
if (n.nodeType === 3) {
return n.data;
/**
* Returns the first text node contained in the supplied node
* @param {NodeElement} node node
* @return {String}
*/
getFirstTextNode(node){
for(let i=0; i<node.childNodes.length; i++){
let n = node.childNodes[i];
if(n.nodeType === 3){
return n.data;
}
}
}
};
},
/**
* Creates an html element with given collection of attributes
* @param {String} tag html tag name
* @param {Array} an undetermined number of arrays containing the with 2
* items, the attribute name and its value ['id','myId']
* @return {Object} created element
*/
export const createElm = (...args) => {
let tag = args[0];
if (!isString(tag)) {
return null;
}
let el = doc.createElement(tag);
for (let i = 0; i < args.length; i++) {
let arg = args[i];
if (isArray(arg) && arg.length === 2) {
el.setAttribute(arg[0], arg[1]);
/**
* Creates an html element with given collection of attributes
* @param {String} tag a string of the html tag to create
* @param {Array} an undetermined number of arrays containing the with 2
* items, the attribute name and its value ['id','myId']
* @return {Object} created element
*/
create(tag){
if(!tag || tag===''){
return;
}
}
return el;
};
/**
* Removes passed node from DOM
* @param {DOMElement} node
* @return {DOMElement} old node reference
*/
export const removeElm = (node) => node.parentNode.removeChild(node);
let el = document.createElement(tag),
args = arguments;
/**
* Returns a text node with given text
* @param {String} txt
* @return {Object}
*/
export const createText = (txt) => doc.createTextNode(txt);
if(args.length > 1){
for(let i=0; i<args.length; i++){
let argtype = typeof args[i];
if(argtype.toLowerCase() === 'object' && args[i].length === 2){
el.setAttribute(args[i][0], args[i][1]);
}
}
}
return el;
},
/**
* Determine whether the passed elements is assigned the given class
* @param {DOMElement} ele DOM element
* @param {String} cls CSS class name
* @returns {Boolean}
*/
export const hasClass = (ele, cls) => {
if (isUndef(ele)) {
return false;
}
/**
* Removes passed node from DOM
* @param {DOMElement} node
* @return {DOMElement} old node reference
*/
remove(node){
return node.parentNode.removeChild(node);
},
if (supportsClassList()) {
return ele.classList.contains(cls);
}
return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
};
/**
* Returns a text node with given text
* @param {String} txt
* @return {Object}
*/
text(txt){
return document.createTextNode(txt);
},
/**
* Adds the specified class to the passed element
* @param {DOMElement} ele DOM element
* @param {String} cls CSS class name
*/
export const addClass = (ele, cls) => {
if (isUndef(ele)) {
return;
}
hasClass(ele, cls){
if(!ele){ return false; }
if (supportsClassList()) {
ele.classList.add(cls);
return;
}
if(supportsClassList()){
return ele.classList.contains(cls);
}
return ele.className.match(new RegExp('(\\s|^)'+ cls +'(\\s|$)'));
},
if (ele.className === '') {
ele.className = cls;
}
else if (!hasClass(ele, cls)) {
ele.className += ' ' + cls;
addClass(ele, cls){
if(!ele){ return; }
if(supportsClassList()){
ele.classList.add(cls);
return;
}
if(ele.className === ''){
ele.className = cls;
}
else if(!this.hasClass(ele, cls)){
ele.className += ' ' + cls;
}
},
removeClass(ele, cls){
if(!ele){ return; }
if(supportsClassList()){
ele.classList.remove(cls);
return;
}
let reg = new RegExp('(\\s|^)'+ cls +'(\\s|$)', 'g');
ele.className = ele.className.replace(reg, '');
},
/**
* Creates and returns an option element
* @param {String} text option text
* @param {String} value option value
* @param {Boolean} isSel whether option is selected
* @return {Object} option element
*/
createOpt(text, value, isSel){
let isSelected = isSel ? true : false,
opt = isSelected ?
this.create('option', ['value',value], ['selected','true']) :
this.create('option', ['value',value]);
opt.appendChild(this.text(text));
return opt;
},
/**
* Creates and returns a checklist item
* @param {Number} chkIndex index of check item
* @param {String} chkValue check item value
* @param {String} labelText check item label text
* @return {Object} li DOM element
*/
createCheckItem(chkIndex, chkValue, labelText){
let li = this.create('li'),
label = this.create('label', ['for', chkIndex]),
check = this.create('input',
['id', chkIndex],
['name', chkIndex],
['type', 'checkbox'],
['value', chkValue]
);
label.appendChild(check);
label.appendChild(this.text(labelText));
li.appendChild(label);
li.label = label;
li.check = check;
return li;
},
id(key){
return document.getElementById(key);
},
tag(o, tagname){
return o.getElementsByTagName(tagname);
}
};
/**
* Removes the specified class to the passed element
* @param {DOMElement} ele DOM element
* @param {String} cls CSS class name
*/
export const removeClass = (ele, cls) => {
if (isUndef(ele)) {
return;
}
if (supportsClassList()) {
ele.classList.remove(cls);
return;
}
let reg = new RegExp('(\\s|^)' + cls + '(\\s|$)', 'g');
ele.className = ele.className.replace(reg, '');
};
/**
* Creates and returns an option element
* @param {String} text option text
* @param {String} value option value
* @param {Boolean} isSel whether option is selected
* @return {Object} option element
*/
export const createOpt = (text, value, isSel) => {
let isSelected = isSel ? true : false;
let opt = isSelected ?
createElm('option', ['value', value], ['selected', 'true']) :
createElm('option', ['value', value]);
opt.appendChild(createText(text));
return opt;
};
/**
* Creates and returns a checklist item
* @param {String} id index of check item
* @param {String} chkValue check item value
* @param {String} labelText check item label text
* @param {Array} extraAttr array containing attribute name and its value
* @return {Object} li DOM element
*/
export const createCheckItem = (id, chkValue, labelText, extraAttr = []) => {
let li = createElm('li');
let label = createElm('label', ['for', id]);
let check = createElm('input',
['id', id],
['name', id],
['type', 'checkbox'],
['value', chkValue],
extraAttr
);
label.appendChild(check);
label.appendChild(createText(labelText));
li.appendChild(label);
li.label = label;
li.check = check;
return li;
};
/**
* Returns the element matching the supplied Id
* @param {String} id Element identifier
* @return {DOMElement}
*/
export const elm = (id) => doc.getElementById(id);
/**
* Returns list of element matching the supplied tag name
* @param {String} tagname Tag name
* @return {NodeList}
*/
export const tag = (o, tagname) => o.getElementsByTagName(tagname);
// HTML5 classList API
function supportsClassList() {
return doc.documentElement.classList;
function supportsClassList(){
return document.documentElement.classList;
}

View file

@ -2,9 +2,6 @@
* Event emitter class
*/
export class Emitter {
/**
* Creates an instance of Emitter.
*/
constructor() {
/**
* Events object
@ -19,7 +16,7 @@ export class Emitter {
* @param {Function} fn Function invoked when event is emitted
*/
on(evts, fn) {
evts.forEach((evt) => {
evts.forEach((evt)=> {
this.events[evt] = this.events[evt] || [];
this.events[evt].push(fn);
});
@ -31,8 +28,8 @@ export class Emitter {
* @param {Function} fn Function invoked when event is emitted
*/
off(evts, fn) {
evts.forEach((evt) => {
if (evt in this.events) {
evts.forEach((evt)=> {
if(evt in this.events) {
this.events[evt].splice(this.events[evt].indexOf(fn), 1);
}
});
@ -44,8 +41,8 @@ export class Emitter {
* the invoked function
*/
emit(evt /*, args...*/) {
if (evt in this.events) {
for (let i = 0; i < this.events[evt].length; i++) {
if(evt in this.events) {
for(let i = 0; i < this.events[evt].length; i++) {
this.events[evt][i].apply(this, [].slice.call(arguments, 1));
}
}

View file

@ -1,124 +1,53 @@
import {root} from './root';
/**
* DOM event utilities
*/
/**
* Add event handler for specified event on passed element
*
* @param {DOMElement} obj Element
* @param {String} type Event type
* @param {Function} Handler
* @param {Boolean} capture Specifiy whether the event should be executed in
* the capturing or in the bubbling phase
*/
export const addEvt = (obj, type, func, capture) => {
if (obj.addEventListener) {
obj.addEventListener(type, func, capture);
}
else if (obj.attachEvent) {
obj.attachEvent('on' + type, func);
} else {
obj['on' + type] = func;
export default {
add(obj, type, func, capture){
if(obj.addEventListener){
obj.addEventListener(type, func, capture);
}
else if(obj.attachEvent){
obj.attachEvent('on'+type, func);
} else {
obj['on'+type] = func;
}
},
remove(obj, type, func, capture){
if(obj.detachEvent){
obj.detachEvent('on'+type,func);
}
else if(obj.removeEventListener){
obj.removeEventListener(type, func, capture);
} else {
obj['on'+type] = null;
}
},
stop(evt){
if(!evt){
evt = window.event;
}
if(evt.stopPropagation){
evt.stopPropagation();
} else {
evt.cancelBubble = true;
}
},
cancel(evt){
if(!evt){
evt = window.event;
}
if(evt.preventDefault) {
evt.preventDefault();
} else {
evt.returnValue = false;
}
},
target(evt){
return (evt && evt.target) || (window.event && window.event.srcElement);
},
keyCode(evt){
return evt.charCode ? evt.charCode :
(evt.keyCode ? evt.keyCode: (evt.which ? evt.which : 0));
}
};
/**
* Remove event handler for specified event on passed element
*
* @param {DOMElement} obj Element
* @param {String} type Event type
* @param {Function} Handler
* @param {Boolean} capture Specifiy whether the event should be executed in
* the capturing or in the bubbling phase
*/
export const removeEvt = (obj, type, func, capture) => {
if (obj.removeEventListener) {
obj.removeEventListener(type, func, capture);
} else if (obj.detachEvent) {
obj.detachEvent('on' + type, func);
} else {
obj['on' + type] = null;
}
};
/**
* Prevents further propagation of the current event in the bubbling phase
*
* @param {Event} evt Event on the DOM
*/
export const stopEvt = (evt) => {
if (!evt) {
evt = root.event;
}
if (evt.stopPropagation) {
evt.stopPropagation();
} else {
evt.cancelBubble = true;
}
};
/**
* Cancels the event if it is cancelable, without stopping further
* propagation of the event.
*
* @param {Event} evt Event on the DOM
*/
export const cancelEvt = (evt) => {
if (!evt) {
evt = root.event;
}
if (evt.preventDefault) {
evt.preventDefault();
} else {
evt.returnValue = false;
}
};
/**
* Reference to the object that dispatched the event
*
* @param {Event} evt Event on the DOM
* @returns {DOMElement}
*/
export const targetEvt = (evt) => {
if (!evt) {
evt = root.event;
}
return evt.target || evt.srcElement;
};
/**
* Returns the Unicode value of pressed key
*
* @param {Event} evt Event on the DOM
* @returns {Number}
*/
export const keyCode = (evt) => {
return evt.charCode ? evt.charCode :
(evt.keyCode ? evt.keyCode : (evt.which ? evt.which : 0));
};
/**
* Check code of pressed key is one of the expected key codes
*
* @param {Event} evt key event
* @param {Array} keyCodes list of keycodes to check
*/
export const isKeyPressed = (evt, keyCodes = []) => {
return keyCodes.indexOf(keyCode(evt)) !== -1;
};
/**
* Bind passed function to passed scope
* @param {Function} fn function
* @param {Object} scope object instance
*/
export function bound(fn, scope) {
let boundFnName = `${fn.name}_bound`;
if (!scope[boundFnName]) {
scope[boundFnName] = fn.bind(scope);
}
return scope[boundFnName];
}

View file

@ -1,190 +1,122 @@
import {Feature} from '../../feature';
import {tag} from '../../dom';
import {INPUT} from '../../const';
import {defaultsStr} from '../../settings';
import {root} from '../../root';
const INSTANTIATION_ERROR = `Failed to instantiate EditTable object.
\n"ezEditTable" dependency not found.`;
/**
* Adapter module for ezEditTable, an external library providing advanced
* grid features (selection and edition):
* http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus
*/
export default class AdapterEzEditTable extends Feature {
import Dom from '../../dom';
export default class AdapterEzEditTable {
/**
* Creates an instance of AdapterEzEditTable
* Adapter module for ezEditTable, an external library providing advanced
* grid features (selection and edition):
* http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus
*
* @param {TableFilter} tf TableFilter instance
* @param {Object} cfg Configuration options for ezEditTable library
* @param {Object} tf TableFilter instance
*/
constructor(tf, cfg) {
super(tf, AdapterEzEditTable);
/**
* Module description
* @type {String}
*/
this.desc = defaultsStr(cfg.description, 'ezEditTable adapter');
/**
* Filename of ezEditTable library
* @type {String}
*/
this.filename = defaultsStr(cfg.filename, 'ezEditTable.js');
/**
* Path to ezEditTable library
* @type {String}
*/
constructor(tf, cfg){
// ezEditTable config
this.initialized = false;
this.desc = cfg.description || 'ezEditTable adapter';
this.filename = cfg.filename || 'ezEditTable.js';
this.vendorPath = cfg.vendor_path;
/**
* Load ezEditTable stylesheet
* @type {Boolean}
*/
this.loadStylesheet = Boolean(cfg.load_stylesheet);
/**
* Path to ezEditTable stylesheet
* @type {String}
*/
this.stylesheet = defaultsStr(cfg.stylesheet,
this.vendorPath + 'ezEditTable.css');
/**
* Name of ezEditTable stylesheet
* @type {String}
*/
this.stylesheetName = defaultsStr(cfg.stylesheet_name,
'ezEditTableCss');
this.stylesheet = cfg.stylesheet || this.vendorPath + 'ezEditTable.css';
this.stylesheetName = cfg.stylesheet_name || 'ezEditTableCss';
this.err = 'Failed to instantiate EditTable object.\n"ezEditTable" ' +
'dependency not found.';
// Enable the ezEditTable's scroll into view behaviour if grid layout on
cfg.scroll_into_view = cfg.scroll_into_view === false ?
cfg.scroll_into_view = cfg.scroll_into_view===false ?
false : tf.gridLayout;
/**
* ezEditTable instance
* @type {EditTable}
* @private
*/
this._ezEditTable = null;
/**
* ezEditTable configuration
* @private
*/
this.cfg = cfg;
this.enable();
this.tf = tf;
this.emitter = tf.emitter;
}
/**
* Conditionally load ezEditTable library and set advanced grid
* @return {[type]} [description]
*/
init() {
if (this.initialized) {
return;
}
let tf = this.tf;
if (root.EditTable) {
init(){
var tf = this.tf;
if(window.EditTable){
this._setAdvancedGrid();
} else {
let path = this.vendorPath + this.filename;
tf.import(this.filename, path, () => this._setAdvancedGrid());
var path = this.vendorPath + this.filename;
tf.import(this.filename, path, ()=> { this._setAdvancedGrid(); });
}
if (this.loadStylesheet && !tf.isImported(this.stylesheet, 'link')) {
if(this.loadStylesheet && !tf.isImported(this.stylesheet, 'link')){
tf.import(this.stylesheetName, this.stylesheet, null, 'link');
}
// TODO: hack to prevent ezEditTable enter key event hijaking.
// Needs to be fixed in the vendor's library
this.emitter.on(['filter-focus', 'filter-blur'],
() => this._toggleForInputFilter());
/**
* @inherited
*/
this.initialized = true;
()=> this._toggleForInputFilter());
}
/**
* Instantiate ezEditTable component for advanced grid features
* @private
*/
_setAdvancedGrid() {
let tf = this.tf;
_setAdvancedGrid(){
var tf = this.tf;
//start row for EditTable constructor needs to be calculated
let startRow,
var startRow,
cfg = this.cfg,
thead = tag(tf.dom(), 'thead');
thead = Dom.tag(tf.tbl, 'thead');
//if thead exists and startRow not specified, startRow is calculated
//automatically by EditTable
if (thead.length > 0 && !cfg.startRow) {
if(thead.length > 0 && !cfg.startRow){
startRow = undefined;
}
//otherwise startRow config property if any or TableFilter refRow
else {
else{
startRow = cfg.startRow || tf.refRow;
}
cfg.base_path = cfg.base_path || tf.basePath + 'ezEditTable/';
let editable = cfg.editable;
let selectable = cfg.selection;
cfg.base_path = cfg.base_path || tf.basePath + 'ezEditTable/';
var editable = cfg.editable;
var selectable = cfg.selection;
if (selectable) {
if(selectable){
cfg.default_selection = cfg.default_selection || 'row';
}
//CSS Styles
cfg.active_cell_css = cfg.active_cell_css || 'ezETSelectedCell';
let _lastValidRowIndex = 0;
let _lastRowIndex = 0;
var _lastValidRowIndex = 0;
var _lastRowIndex = 0;
if (selectable) {
if(selectable){
//Row navigation needs to be calculated according to TableFilter's
//validRowsIndex array
let onAfterSelection = function (et, selectedElm, e) {
let slc = et.Selection;
var onAfterSelection = function(et, selectedElm, e){
var slc = et.Selection;
//Next valid filtered row needs to be selected
let doSelect = function (nextRowIndex) {
if (et.defaultSelection === 'row') {
/* eslint-disable */
var doSelect = function(nextRowIndex){
if(et.defaultSelection === 'row'){
slc.SelectRowByIndex(nextRowIndex);
/* eslint-enable */
} else {
/* eslint-disable */
et.ClearSelections();
/* eslint-enable */
let cellIndex = selectedElm.cellIndex,
row = tf.dom().rows[nextRowIndex];
if (et.defaultSelection === 'both') {
/* eslint-disable */
var cellIndex = selectedElm.cellIndex,
row = tf.tbl.rows[nextRowIndex];
if(et.defaultSelection === 'both'){
slc.SelectRowByIndex(nextRowIndex);
/* eslint-enable */
}
if (row) {
/* eslint-disable */
if(row){
slc.SelectCell(row.cells[cellIndex]);
/* eslint-enable */
}
}
//Table is filtered
if (tf.validRowsIndex.length !== tf.getRowsNb()) {
let r = tf.dom().rows[nextRowIndex];
if (r) {
if(tf.validRowsIndex.length !== tf.getRowsNb()){
var r = tf.tbl.rows[nextRowIndex];
if(r){
r.scrollIntoView(false);
}
if (cell) {
if (cell.cellIndex === (tf.getCellsNb() - 1) &&
tf.gridLayout) {
if(cell){
if(cell.cellIndex === (tf.getCellsNb()-1) &&
tf.gridLayout){
tf.tblCont.scrollLeft = 100000000;
}
else if (cell.cellIndex === 0 && tf.gridLayout) {
else if(cell.cellIndex===0 && tf.gridLayout){
tf.tblCont.scrollLeft = 0;
} else {
cell.scrollIntoView(false);
@ -194,48 +126,45 @@ export default class AdapterEzEditTable extends Feature {
};
//table is not filtered
if (!tf.validRowsIndex) {
if(!tf.validRowsIndex){
return;
}
let validIndexes = tf.validRowsIndex,
var validIndexes = tf.validRowsIndex,
validIdxLen = validIndexes.length,
row = et.defaultSelection !== 'row' ?
selectedElm.parentNode : selectedElm,
//cell for default_selection = 'both' or 'cell'
cell = selectedElm.nodeName === 'TD' ? selectedElm : null,
/* eslint-disable */
cell = selectedElm.nodeName==='TD' ? selectedElm : null,
keyCode = e !== undefined ? et.Event.GetKey(e) : 0,
/* eslint-enable */
isRowValid = validIndexes.indexOf(row.rowIndex) !== -1,
nextRowIndex,
paging = tf.feature('paging'),
//pgup/pgdown keys
d = keyCode === 34 || keyCode === 33 ?
(paging && paging.pageLength || et.nbRowsPerPage) :
1;
d = (keyCode === 34 || keyCode === 33 ?
(paging && paging.pagingLength || et.nbRowsPerPage) :1);
//If next row is not valid, next valid filtered row needs to be
//calculated
if (!isRowValid) {
if(!isRowValid){
//Selection direction up/down
if (row.rowIndex > _lastRowIndex) {
if(row.rowIndex>_lastRowIndex){
//last row
if (row.rowIndex >= validIndexes[validIdxLen - 1]) {
nextRowIndex = validIndexes[validIdxLen - 1];
if(row.rowIndex >= validIndexes[validIdxLen-1]){
nextRowIndex = validIndexes[validIdxLen-1];
} else {
let calcRowIndex = (_lastValidRowIndex + d);
if (calcRowIndex > (validIdxLen - 1)) {
nextRowIndex = validIndexes[validIdxLen - 1];
var calcRowIndex = (_lastValidRowIndex + d);
if(calcRowIndex > (validIdxLen-1)){
nextRowIndex = validIndexes[validIdxLen-1];
} else {
nextRowIndex = validIndexes[calcRowIndex];
}
}
} else {
} else{
//first row
if (row.rowIndex <= validIndexes[0]) {
if(row.rowIndex <= validIndexes[0]){
nextRowIndex = validIndexes[0];
} else {
let v = validIndexes[_lastValidRowIndex - d];
var v = validIndexes[_lastValidRowIndex - d];
nextRowIndex = v ? v : validIndexes[0];
}
}
@ -244,21 +173,21 @@ export default class AdapterEzEditTable extends Feature {
} else {
//If filtered row is valid, special calculation for
//pgup/pgdown keys
if (keyCode !== 34 && keyCode !== 33) {
if(keyCode!==34 && keyCode!==33){
_lastValidRowIndex = validIndexes.indexOf(row.rowIndex);
_lastRowIndex = row.rowIndex;
} else {
if (keyCode === 34) { //pgdown
if(keyCode === 34){ //pgdown
//last row
if ((_lastValidRowIndex + d) <= (validIdxLen - 1)) {
if((_lastValidRowIndex + d) <= (validIdxLen-1)){
nextRowIndex = validIndexes[
_lastValidRowIndex + d];
_lastValidRowIndex + d];
} else {
nextRowIndex = [validIdxLen - 1];
nextRowIndex = [validIdxLen-1];
}
} else { //pgup
//first row
if ((_lastValidRowIndex - d) <= validIndexes[0]) {
if((_lastValidRowIndex - d) <= validIndexes[0]){
nextRowIndex = validIndexes[0];
} else {
nextRowIndex = validIndexes[
@ -274,35 +203,35 @@ export default class AdapterEzEditTable extends Feature {
//Page navigation has to be enforced whenever selected row is out of
//the current page range
let onBeforeSelection = function (et, selectedElm) {
let row = et.defaultSelection !== 'row' ?
var onBeforeSelection = function(et, selectedElm){
var row = et.defaultSelection !== 'row' ?
selectedElm.parentNode : selectedElm;
if (tf.paging) {
if (tf.feature('paging').nbPages > 1) {
let paging = tf.feature('paging');
if(tf.paging){
if(tf.feature('paging').nbPages > 1){
var paging = tf.feature('paging');
//page length is re-assigned in case it has changed
et.nbRowsPerPage = paging.pageLength;
let validIndexes = tf.validRowsIndex,
et.nbRowsPerPage = paging.pagingLength;
var validIndexes = tf.validRowsIndex,
validIdxLen = validIndexes.length,
pagingEndRow = parseInt(paging.startPagingRow, 10) +
parseInt(paging.pageLength, 10);
let rowIndex = row.rowIndex;
parseInt(paging.pagingLength, 10);
var rowIndex = row.rowIndex;
if ((rowIndex === validIndexes[validIdxLen - 1]) &&
paging.currentPageNb !== paging.nbPages) {
if((rowIndex === validIndexes[validIdxLen-1]) &&
paging.currentPageNb!==paging.nbPages){
paging.setPage('last');
}
else if ((rowIndex === validIndexes[0]) &&
paging.currentPageNb !== 1) {
else if((rowIndex == validIndexes[0]) &&
paging.currentPageNb!==1){
paging.setPage('first');
}
else if (rowIndex > validIndexes[pagingEndRow - 1] &&
rowIndex < validIndexes[validIdxLen - 1]) {
else if(rowIndex > validIndexes[pagingEndRow-1] &&
rowIndex < validIndexes[validIdxLen-1]){
paging.setPage('next');
}
else if (
else if(
rowIndex < validIndexes[paging.startPagingRow] &&
rowIndex > validIndexes[0]) {
rowIndex > validIndexes[0]){
paging.setPage('previous');
}
}
@ -310,21 +239,17 @@ export default class AdapterEzEditTable extends Feature {
};
//Selected row needs to be visible when paging is activated
if (tf.paging) {
tf.feature('paging').onAfterChangePage = function (paging) {
let advGrid = paging.tf.extension('advancedGrid');
let et = advGrid._ezEditTable;
let slc = et.Selection;
/* eslint-disable */
let row = slc.GetActiveRow();
/* eslint-enable */
if (row) {
if(tf.paging){
tf.feature('paging').onAfterChangePage = function(paging){
var advGrid = paging.tf.extension('advancedGrid');
var et = advGrid._ezEditTable;
var slc = et.Selection;
var row = slc.GetActiveRow();
if(row){
row.scrollIntoView(false);
}
/* eslint-disable */
let cell = slc.GetActiveCell();
/* eslint-enable */
if (cell) {
var cell = slc.GetActiveCell();
if(cell){
cell.scrollIntoView(false);
}
};
@ -332,92 +257,94 @@ export default class AdapterEzEditTable extends Feature {
//Rows navigation when rows are filtered is performed with the
//EditTable row selection callback events
if (cfg.default_selection === 'row') {
let fnB = cfg.on_before_selected_row;
cfg.on_before_selected_row = function () {
var args = arguments;
onBeforeSelection(args[0], args[1], args[2]);
if (fnB) {
fnB.call(null, args[0], args[1], args[2]);
if(cfg.default_selection==='row'){
var fnB = cfg.on_before_selected_row;
cfg.on_before_selected_row = function(){
onBeforeSelection(arguments[0], arguments[1], arguments[2]);
if(fnB){
fnB.call(
null, arguments[0], arguments[1], arguments[2]);
}
};
let fnA = cfg.on_after_selected_row;
cfg.on_after_selected_row = function () {
var args = arguments;
onAfterSelection(args[0], args[1], args[2]);
if (fnA) {
fnA.call(null, args[0], args[1], args[2]);
var fnA = cfg.on_after_selected_row;
cfg.on_after_selected_row = function(){
onAfterSelection(arguments[0], arguments[1], arguments[2]);
if(fnA){
fnA.call(
null, arguments[0], arguments[1], arguments[2]);
}
};
} else {
let fnD = cfg.on_before_selected_cell;
cfg.on_before_selected_cell = function () {
var args = arguments;
onBeforeSelection(args[0], args[1], args[2]);
if (fnD) {
fnD.call(null, args[0], args[1], args[2]);
var fnD = cfg.on_before_selected_cell;
cfg.on_before_selected_cell = function(){
onBeforeSelection(arguments[0], arguments[1], arguments[2]);
if(fnD){
fnD.call(
null, arguments[0], arguments[1], arguments[2]);
}
};
let fnC = cfg.on_after_selected_cell;
cfg.on_after_selected_cell = function () {
var args = arguments;
onAfterSelection(args[0], args[1], args[2]);
if (fnC) {
fnC.call(null, args[0], args[1], args[2]);
var fnC = cfg.on_after_selected_cell;
cfg.on_after_selected_cell = function(){
onAfterSelection(arguments[0], arguments[1], arguments[2]);
if(fnC){
fnC.call(
null, arguments[0], arguments[1], arguments[2]);
}
};
}
}
if (editable) {
if(editable){
//Added or removed rows, TF rows number needs to be re-calculated
let fnE = cfg.on_added_dom_row;
cfg.on_added_dom_row = function () {
var args = arguments;
var fnE = cfg.on_added_dom_row;
cfg.on_added_dom_row = function(){
tf.nbFilterableRows++;
if (!tf.paging) {
if(!tf.paging){
tf.emitter.emit('rows-changed', tf, this);
//tf.feature('rowsCounter').refresh();
} else {
tf.nbRows++;
tf.nbVisibleRows++;
tf.nbFilterableRows++;
tf.paging = false;
tf.paging=false;
tf.feature('paging').destroy();
tf.feature('paging').reset();
}
if (tf.alternateRows) {
if(tf.alternateRows){
tf.feature('alternateRows').init();
}
if (fnE) {
fnE.call(null, args[0], args[1], args[2]);
if(fnE){
fnE.call(null, arguments[0], arguments[1], arguments[2]);
}
};
if (cfg.actions && cfg.actions['delete']) {
let fnF = cfg.actions['delete'].on_after_submit;
cfg.actions['delete'].on_after_submit = function () {
var args = arguments;
if(cfg.actions && cfg.actions['delete']){
var fnF = cfg.actions['delete'].on_after_submit;
cfg.actions['delete'].on_after_submit = function(){
tf.nbFilterableRows--;
if (!tf.paging) {
if(!tf.paging){
// tf.feature('rowsCounter').refresh();
tf.emitter.emit('rows-changed', tf, this);
} else {
tf.nbRows--;
tf.nbVisibleRows--;
tf.nbFilterableRows--;
tf.paging = false;
tf.paging=false;
tf.feature('paging').destroy();
tf.feature('paging').reset(false);
}
if (tf.alternateRows) {
if(tf.alternateRows){
tf.feature('alternateRows').init();
}
if (fnF) {
fnF.call(null, args[0], args[1]);
if(fnF){
fnF.call(null, arguments[0], arguments[1]);
}
};
}
}
try {
/* eslint-disable */
try{
this._ezEditTable = new EditTable(tf.id, cfg, startRow);
this._ezEditTable.Init();
/* eslint-enable */
} catch (e) { throw new Error(INSTANTIATION_ERROR); }
} catch(e) { throw new Error(this.err); }
this.initialized = true;
}
@ -425,18 +352,14 @@ export default class AdapterEzEditTable extends Feature {
/**
* Reset advanced grid when previously removed
*/
reset() {
let ezEditTable = this._ezEditTable;
if (ezEditTable) {
if (this.cfg.selection) {
/* eslint-disable */
reset(){
var ezEditTable = this._ezEditTable;
if(ezEditTable){
if(this.cfg.selection){
ezEditTable.Selection.Set();
/* eslint-enable */
}
if (this.cfg.editable) {
/* eslint-disable */
if(this.cfg.editable){
ezEditTable.Editable.Set();
/* eslint-enable */
}
}
}
@ -444,36 +367,28 @@ export default class AdapterEzEditTable extends Feature {
/**
* Toggle behaviour
*/
toggle() {
let ezEditTable = this._ezEditTable;
if (ezEditTable.editable) {
/* eslint-disable */
toggle(){
var ezEditTable = this._ezEditTable;
if(ezEditTable.editable){
ezEditTable.Editable.Remove();
/* eslint-enable */
} else {
/* eslint-disable */
ezEditTable.Editable.Set();
/* eslint-enable */
}
if (ezEditTable.selection) {
/* eslint-disable */
if(ezEditTable.selection){
ezEditTable.Selection.Remove();
/* eslint-enable */
} else {
/* eslint-disable */
ezEditTable.Selection.Set();
/* eslint-enable */
}
}
_toggleForInputFilter() {
let tf = this.tf;
if (!tf.getActiveFilterId()) {
_toggleForInputFilter(){
var tf = this.tf;
if(!tf.activeFlt){
return;
}
let colIndex = tf.getColumnIndexFromFilterId(tf.getActiveFilterId());
let filterType = tf.getFilterType(colIndex);
if (filterType === INPUT) {
var colIndex = tf.activeFlt.getAttribute('ct');
var filterType = tf.getFilterType(colIndex);
if(filterType === tf.fltTypeInp){
this.toggle();
}
}
@ -481,29 +396,20 @@ export default class AdapterEzEditTable extends Feature {
/**
* Remove advanced grid
*/
destroy() {
if (!this.initialized) {
return;
}
let ezEditTable = this._ezEditTable;
if (ezEditTable) {
if (this.cfg.selection) {
/* eslint-disable */
destroy(){
var ezEditTable = this._ezEditTable;
if(ezEditTable){
if(this.cfg.selection){
ezEditTable.Selection.ClearSelections();
ezEditTable.Selection.Remove();
/* eslint-enable */
}
if (this.cfg.editable) {
/* eslint-disable */
if(this.cfg.editable){
ezEditTable.Editable.Remove();
/* eslint-enable */
}
}
this.emitter.off(['filter-focus', 'filter-blur'],
() => this._toggleForInputFilter());
()=> this._toggleForInputFilter());
this.initialized = false;
}
}
AdapterEzEditTable.meta = {altName: 'advancedGrid'};

View file

@ -1,127 +1,31 @@
import {Feature} from '../../feature';
import {createText, elm} from '../../dom';
import {isArray, isEmpty, EMPTY_FN} from '../../types';
import {numSortAsc} from '../../sort';
import {FORMATTED_NUMBER} from '../../const';
import formatNumber from 'format-number';
import {defaultsFn, defaultsArr} from '../../settings';
import {bound} from '../../event';
import Dom from '../../dom';
import Str from '../../string';
import Types from '../../types';
const EVENTS = [
'after-filtering',
'after-page-change',
'after-page-length-change'
];
const SUM = 'sum';
const MEAN = 'mean';
const MIN = 'min';
const MAX = 'max';
const MEDIAN = 'median';
const Q1 = 'q1';
const Q3 = 'q3';
/**
* Column calculations extension
*/
export default class ColOps extends Feature {
export default class ColOps{
/**
* Creates an instance of ColOps
*
* @param {TableFilter} tf TableFilter instance
* @param {Object} opts Configuration object
* Column calculations
* @param {Object} tf TableFilter instance
*/
constructor(tf, opts) {
super(tf, ColOps);
/**
* Callback fired before columns operations start
* @type {Function}
*/
this.onBeforeOperation = defaultsFn(opts.on_before_operation, EMPTY_FN);
//calls function before col operation
this.onBeforeOperation = Types.isFn(opts.on_before_operation) ?
opts.on_before_operation : null;
//calls function after col operation
this.onAfterOperation = Types.isFn(opts.on_after_operation) ?
opts.on_after_operation : null;
/**
* Callback fired after columns operations are completed
* @type {Function}
*/
this.onAfterOperation = defaultsFn(opts.on_after_operation, EMPTY_FN);
/**
* Configuration options
* @type {Object}
*/
this.opts = opts;
/**
* List of DOM element IDs containing column's calculation result
* @type {Array}
*/
this.labelIds = defaultsArr(opts.id, []);
/**
* List of columns' indexes for calculations
* @type {Array}
*/
this.colIndexes = defaultsArr(opts.col, []);
/**
* List of operations - possible values: 'sum', 'mean', 'min', 'max',
* 'median', 'q1', 'q3'
* @type {Array}
*/
this.operations = defaultsArr(opts.operation, []);
/**
* List of write methods used to write the result - possible values:
* 'innerHTML', 'setValue', 'createTextNode'
* @type {Array}
*/
this.outputTypes = defaultsArr(opts.write_method, []);
/**
* List of format objects used for formatting the result -
* refer to https://github.com/componitable/format-number to check
* configuration options
* @type {Array}
*/
this.formatResults = defaultsArr(opts.format_result, []);
/**
* List of row indexes displaying the results
* @type {Array}
*/
this.totRowIndexes = defaultsArr(opts.tot_row_index, []);
/**
* List of row indexes excluded from calculations
* @type {Array}
*/
this.excludeRows = defaultsArr(opts.exclude_row, []);
/**
* List of decimal precision for calculation results
* @type {Array}
*/
this.decimalPrecisions = defaultsArr(opts.decimal_precision, 2);
this.enable();
this.tf = tf;
}
/**
* Initializes ColOps instance
*/
init() {
if (this.initialized) {
return;
}
init(){
// subscribe to events
this.emitter.on(EVENTS, bound(this.calcAll, this));
this.tf.emitter.on(['after-filtering'], ()=> this.calc());
this.calcAll();
/** @inherited */
this.initialized = true;
this.calc();
}
/**
@ -140,341 +44,278 @@ export default class ColOps extends Feature {
* (1) optimized the routine (now it will only process each column once),
* (2) added calculations for the median, lower and upper quartile.
*/
calcAll() {
let tf = this.tf;
if (!tf.isInitialized()) {
calc() {
var tf = this.tf;
if(!tf.hasGrid()){
return;
}
this.onBeforeOperation(tf, this);
this.emitter.emit('before-column-operation', tf, this);
if(this.onBeforeOperation){
this.onBeforeOperation.call(null, tf);
}
let { colIndexes, operations: colOperations, outputTypes,
totRowIndexes, excludeRows, formatResults,
decimalPrecisions } = this;
var opts = this.opts,
labelId = opts.id,
colIndex = opts.col,
operation = opts.operation,
outputType = opts.write_method,
totRowIndex = opts.tot_row_index,
excludeRow = opts.exclude_row,
decimalPrecision = Types.isUndef(opts.decimal_precision) ?
2 : opts.decimal_precision;
//nuovella: determine unique list of columns to operate on
let uIndexes = [];
colIndexes.forEach((val) => {
if (uIndexes.indexOf(val) === -1) {
uIndexes.push(val);
}
});
var ucolIndex = [],
ucolMax = 0;
ucolIndex[ucolMax] = colIndex[0];
let nbCols = uIndexes.length,
rows = tf.dom().rows,
colValues = [];
for (let u = 0; u < nbCols; u++) {
//this retrieves col values
//use uIndexes because we only want to pass through this loop
//once for each column get the values in this unique column
colValues.push(
tf.getVisibleColumnData(uIndexes[u], false, excludeRows)
);
let curValues = colValues[u];
//next: calculate all operations for this column
let result = 0,
operations = [],
precisions = [],
labels = [],
writeType,
formatResult = [],
idx = 0;
for (let k = 0; k < colIndexes.length; k++) {
if (colIndexes[k] !== uIndexes[u]) {
continue;
for(var ii=1; ii<colIndex.length; ii++){
var saved = 0;
//see if colIndex[ii] is already in the list of unique indexes
for(var jj=0; jj<=ucolMax; jj++){
if(ucolIndex[jj] === colIndex[ii]){
saved = 1;
}
operations[idx] = (colOperations[k] || 'sum').toLowerCase();
precisions[idx] = decimalPrecisions[k];
labels[idx] = this.labelIds[k];
writeType = isArray(outputTypes) ? outputTypes[k] : null;
formatResult[idx] =
this.configureFormat(uIndexes[u], formatResults[k]);
idx++;
}
for (let i = 0; i < idx; i++) {
// emit values before column calculation
this.emitter.emit(
'before-column-calc',
tf,
this,
uIndexes[u],
curValues,
operations[i],
precisions[i]
);
result = Number(this.calc(curValues, operations[i], null));
// emit column calculation result
this.emitter.emit(
'column-calc',
tf,
this,
uIndexes[u],
result,
operations[i],
precisions[i]
);
// write result in expected DOM element
this.writeResult(
result,
labels[i],
writeType,
precisions[i],
formatResult[i]
);
}//for i
// row(s) with result are always visible
let totRow = totRowIndexes && totRowIndexes[u] ?
rows[totRowIndexes[u]] : null;
if (totRow) {
totRow.style.display = '';
//if not saved then, save the index;
if (saved === 0){
ucolMax++;
ucolIndex[ucolMax] = colIndex[ii];
}
}//for u
this.onAfterOperation(tf, this);
this.emitter.emit('after-column-operation', tf, this);
}
/**
* Make desired calculation on specified column.
* @param {Number} colIndex Column index
* @param {String} [operation=SUM] Operation type
* @param {Number} precision Decimal precision
* @returns {Number}
*/
columnCalc(colIndex, operation = SUM, precision) {
let excludeRows = this.excludeRows || [];
let colValues = tf.getVisibleColumnData(colIndex, false, excludeRows);
return Number(this.calc(colValues, operation, precision));
}
/**
* Make calculation on passed values.
* @param {Array} values List of values
* @param {String} [operation=SUM] Optional operation type
* @param {Number} precision Optional result precision
* @returns {Number}
* @private
*/
calc(colValues, operation = SUM, precision) {
let result = 0;
if (operation === Q1 || operation === Q3 || operation === MEDIAN) {
colValues = this.sortColumnValues(colValues, numSortAsc);
}
switch (operation) {
case MEAN:
result = this.calcMean(colValues);
break;
case SUM:
result = this.calcSum(colValues);
break;
case MIN:
result = this.calcMin(colValues);
break;
case MAX:
result = this.calcMax(colValues);
break;
case MEDIAN:
result = this.calcMedian(colValues);
break;
case Q1:
result = this.calcQ1(colValues);
break;
case Q3:
result = this.calcQ3(colValues);
break;
}
if(Str.lower(typeof labelId)=='object' &&
Str.lower(typeof colIndex)=='object' &&
Str.lower(typeof operation)=='object'){
var rows = tf.tbl.rows,
colvalues = [];
return isEmpty(precision) ? result : result.toFixed(precision);
}
for(var ucol=0; ucol<=ucolMax; ucol++){
//this retrieves col values
//use ucolIndex because we only want to pass through this loop
//once for each column get the values in this unique column
colvalues.push(
tf.getColValues(ucolIndex[ucol], false, true, excludeRow));
/**
* Calculate the sum of passed values.
* @param {Array} [values=[]] List of values
* @returns {Number}
*/
calcSum(values = []) {
if (isEmpty(values)) {
return 0;
}
let result = values.reduce((x, y) => Number(x) + Number(y));
return result;
}
//next: calculate all operations for this column
var result,
nbvalues=0,
temp,
meanValue=0,
sumValue=0,
minValue=null,
maxValue=null,
q1Value=null,
medValue=null,
q3Value=null,
meanFlag=0,
sumFlag=0,
minFlag=0,
maxFlag=0,
q1Flag=0,
medFlag=0,
q3Flag=0,
theList=[],
opsThisCol=[],
decThisCol=[],
labThisCol=[],
oTypeThisCol=[],
mThisCol=-1;
/**
* Calculate the mean of passed values.
* @param {Array} [values=[]] List of values
* @returns {Number}
*/
calcMean(values = []) {
let result = this.calcSum(values) / values.length;
return Number(result);
}
for(var k=0; k<colIndex.length; k++){
if(colIndex[k] === ucolIndex[ucol]){
mThisCol++;
opsThisCol[mThisCol]=Str.lower(operation[k]);
decThisCol[mThisCol]=decimalPrecision[k];
labThisCol[mThisCol]=labelId[k];
oTypeThisCol = outputType !== undefined &&
Str.lower(typeof outputType)==='object' ?
outputType[k] : null;
/**
* Calculate the max value of passed values.
* @param {Array} [values=[]] List of values
* @returns {Number}
*/
calcMax(values = []) {
return Math.max.apply(null, values);
}
switch(opsThisCol[mThisCol]){
case 'mean':
meanFlag=1;
break;
case 'sum':
sumFlag=1;
break;
case 'min':
minFlag=1;
break;
case 'max':
maxFlag=1;
break;
case 'median':
medFlag=1;
break;
case 'q1':
q1Flag=1;
break;
case 'q3':
q3Flag=1;
break;
}
}
}
/**
* Calculate the min value of passed values.
* @param {Array} [values=[]] List of values
* @returns {Number}
*/
calcMin(values = []) {
return Math.min.apply(null, values);
}
for(var j=0; j<colvalues[ucol].length; j++){
//sort the list for calculation of median and quartiles
if((q1Flag==1)|| (q3Flag==1) || (medFlag==1)){
if (j<colvalues[ucol].length -1){
for(k=j+1; k<colvalues[ucol].length; k++) {
if(eval(colvalues[ucol][k]) <
eval(colvalues[ucol][j])){
temp = colvalues[ucol][j];
colvalues[ucol][j] = colvalues[ucol][k];
colvalues[ucol][k] = temp;
}
}
}
}
var cvalue = parseFloat(colvalues[ucol][j]);
theList[j] = parseFloat(cvalue);
/**
* Calculate the median of passed values.
* @param {Array} [values=[]] List of values
* @returns {Number}
*/
calcMedian(values = []) {
let nbValues = values.length;
let aux = 0;
if (nbValues % 2 === 1) {
aux = Math.floor(nbValues / 2);
return Number(values[aux]);
}
return (Number(values[nbValues / 2]) +
Number(values[((nbValues / 2) - 1)])) / 2;
}
if(!isNaN(cvalue)){
nbvalues++;
if(sumFlag===1 || meanFlag===1){
sumValue += parseFloat( cvalue );
}
if(minFlag===1){
if(minValue===null){
minValue = parseFloat( cvalue );
} else{
minValue = parseFloat( cvalue ) < minValue ?
parseFloat( cvalue ): minValue;
}
}
if(maxFlag===1){
if (maxValue===null){
maxValue = parseFloat( cvalue );
} else {
maxValue = parseFloat( cvalue ) > maxValue ?
parseFloat( cvalue ): maxValue;
}
}
}
}//for j
if(meanFlag===1){
meanValue = sumValue/nbvalues;
}
if(medFlag===1){
var aux = 0;
if(nbvalues%2 === 1){
aux = Math.floor(nbvalues/2);
medValue = theList[aux];
} else{
medValue =
(theList[nbvalues/2] + theList[((nbvalues/2)-1)])/2;
}
}
var posa;
if(q1Flag===1){
posa=0.0;
posa = Math.floor(nbvalues/4);
if(4*posa == nbvalues){
q1Value = (theList[posa-1] + theList[posa])/2;
} else {
q1Value = theList[posa];
}
}
if (q3Flag===1){
posa=0.0;
var posb=0.0;
posa = Math.floor(nbvalues/4);
if (4*posa === nbvalues){
posb = 3*posa;
q3Value = (theList[posb] + theList[posb-1])/2;
} else {
q3Value = theList[nbvalues-posa-1];
}
}
/**
* Calculate the lower quartile of passed values.
* @param {Array} [values=[]] List of values
* @returns {Number}
*/
calcQ1(values = []) {
let nbValues = values.length;
let posa = 0.0;
posa = Math.floor(nbValues / 4);
if (4 * posa === nbValues) {
return (Number(values[posa - 1]) +
Number(values[posa])) / 2;
}
return Number(values[posa]);
}
for(var i=0; i<=mThisCol; i++){
switch( opsThisCol[i] ){
case 'mean':
result=meanValue;
break;
case 'sum':
result=sumValue;
break;
case 'min':
result=minValue;
break;
case 'max':
result=maxValue;
break;
case 'median':
result=medValue;
break;
case 'q1':
result=q1Value;
break;
case 'q3':
result=q3Value;
break;
}
/**
* Calculate the upper quartile of passed values.
* @param {Array} [values=[]] List of values
* @returns {Number}
*/
calcQ3(values = []) {
let nbValues = values.length;
let posa = 0.0;
let posb = 0.0;
posa = Math.floor(nbValues / 4);
if (4 * posa === nbValues) {
posb = 3 * posa;
return (Number(values[posb]) +
Number(values[posb - 1])) / 2;
}
return Number(values[nbValues - posa - 1]);
}
var precision = !isNaN(decThisCol[i]) ? decThisCol[i] : 2;
/**
* Sort passed values with supplied sorter function.
* @param {Array} [values=[]] List of values to be sorted
* @param {Function} sorter Sorter function
* @returns {Array}
*/
sortColumnValues(values = [], sorter) {
return values.sort(sorter);
}
//if outputType is defined
if(oTypeThisCol && result){
result = result.toFixed( precision );
/**
* Write calculation result in passed DOM element with supplied write method
* and decimal precision.
* @param {Number} [result=0] Calculation result
* @param {DOMElement} label DOM element
* @param {String} [writeType='innerhtml'] Write method
* @param {Number} [precision=2] Applied decimal precision
* @private
*/
writeResult(result = 0, label, writeType = 'innerhtml',
precision = 2, format = {}) {
let labelElm = elm(label);
if(Dom.id(labThisCol[i])){
switch( Str.lower(oTypeThisCol) ){
case 'innerhtml':
if (isNaN(result) || !isFinite(result) ||
nbvalues===0){
Dom.id(labThisCol[i]).innerHTML = '.';
} else{
Dom.id(labThisCol[i]).innerHTML= result;
}
break;
case 'setvalue':
Dom.id( labThisCol[i] ).value = result;
break;
case 'createtextnode':
var oldnode = Dom.id(labThisCol[i])
.firstChild;
var txtnode = Dom.text(result);
Dom.id(labThisCol[i])
.replaceChild(txtnode, oldnode);
break;
}//switch
}
} else {
try{
if(isNaN(result) || !isFinite(result) ||
nbvalues===0){
Dom.id(labThisCol[i]).innerHTML = '.';
} else {
Dom.id(labThisCol[i]).innerHTML =
result.toFixed(precision);
}
} catch(e) {}//catch
}//else
}//for i
if (!labelElm) {
return;
}
// row(s) with result are always visible
var totRow = totRowIndex && totRowIndex[ucol] ?
rows[totRowIndex[ucol]] : null;
if(totRow){
totRow.style.display = '';
}
}//for ucol
}//if typeof
result = result.toFixed(precision);
if (isNaN(result) || !isFinite(result)) {
result = '';
} else {
result = formatNumber(format)(result);
}
switch (writeType.toLowerCase()) {
case 'innerhtml':
labelElm.innerHTML = result;
break;
case 'setvalue':
labelElm.value = result;
break;
case 'createtextnode':
let oldNode = labelElm.firstChild;
let txtNode = createText(result);
labelElm.replaceChild(txtNode, oldNode);
break;
if(this.onAfterOperation){
this.onAfterOperation.call(null, tf);
}
}
/**
* Configure the format options used to format the operation result based
* on column type.
* @param {Number} colIndex Column index
* @param {Object} [format={}] Format object
* @returns {Object}
* @private
*/
configureFormat(colIndex, format = {}) {
let tf = this.tf;
if (tf.hasType(colIndex, [FORMATTED_NUMBER])) {
let colType = tf.colTypes[colIndex];
if (colType.decimal && !format.decimal) {
format.decimal = colType.decimal;
}
if (colType.thousands && !format.integerSeparator) {
format.integerSeparator = colType.thousands;
}
} else {
format.decimal = format.decimal || '';
format.integerSeparator = format.integerSeparator || '';
}
return format;
}
/** Remove extension */
destroy() {
if (!this.initialized) {
return;
}
destroy(){
// unsubscribe to events
this.emitter.off(EVENTS, bound(this.calcAll, this));
this.initialized = false;
this.tf.emitter.off(['after-filtering'], ()=> this.calc());
}
}

View file

@ -1,491 +1,273 @@
import {Feature} from '../../feature';
import {
addClass, removeClass, createCheckItem, createElm, elm, removeElm,
getText, tag
} from '../../dom';
import {isUndef, EMPTY_FN, isNull} from '../../types';
import {addEvt, targetEvt, removeEvt} from '../../event';
import {root} from '../../root';
import {NONE} from '../../const';
import {
defaultsBool, defaultsStr, defaultsFn, defaultsNb, defaultsArr
} from '../../settings';
import {RIGHT} from '../../modules/toolbar';
import Dom from '../../dom';
import Types from '../../types';
import Event from '../../event';
/**
* Columns Visibility extension
*/
export default class ColsVisibility extends Feature {
export default class ColsVisibility{
/**
* Creates an instance of ColsVisibility
* @param {TableFilter} tf TableFilter instance
* @param {Object} Configuration object
* Columns Visibility extension
* @param {Object} tf TableFilter instance
* @param {Object} f Config
*/
constructor(tf, f) {
super(tf, ColsVisibility);
constructor(tf, f){
// Configuration object
let cfg = this.config;
var cfg = tf.config();
/**
* Module name
* @type {String}
*/
this.initialized = false;
this.name = f.name;
this.desc = f.description || 'Columns visibility manager';
/**
* Module description
* @type {String}
*/
this.desc = defaultsStr(f.description, 'Columns visibility manager');
/**
* show/hide columns container element
* @private
*/
//show/hide cols span element
this.spanEl = null;
/**
* show/hide columns button element
* @private
*/
//show/hide cols button element
this.btnEl = null;
/**
* show/hide columns main container element
* @private
*/
//show/hide cols container div element
this.contEl = null;
/**
* Enable tick to hide a column, defaults to true
* @type {Boolean}
*/
this.tickToHide = defaultsBool(f.tick_to_hide, true);
/**
* Enable columns manager UI, defaults to true
* @type {Boolean}
*/
this.manager = defaultsBool(f.manager, true);
/**
* Headers HTML table reference only if headers are external
* @type {DOMElement}
*/
this.headersTbl = f.headers_table || null;
/**
* Headers row index only if headers are external
* @type {Number}
*/
this.headersIndex = defaultsNb(f.headers_index, 1);
/**
* ID of main container element
* @type {String}
*/
this.contElTgtId = defaultsStr(f.container_target_id, null);
/**
* Alternative text for column headers in column manager UI
* @type {Array}
*/
this.headersText = defaultsArr(f.headers_text, []);
/**
* ID of button's container element
* @type {String}
*/
this.btnTgtId = defaultsStr(f.btn_target_id, null);
/**
* Button's text, defaults to Columns&#9660;
* @type {String}
*/
this.btnText = defaultsStr(f.btn_text, 'Columns&#9660;');
/**
* Button's inner HTML
* @type {String}
*/
this.btnHtml = defaultsStr(f.btn_html, null);
/**
* Css class for button
* @type {String}
*/
this.btnCssClass = defaultsStr(f.btn_css_class, 'colVis');
/**
* Columns manager UI close link text, defaults to 'Close'
* @type {String}
*/
this.btnCloseText = defaultsStr(f.btn_close_text, 'Close');
/**
* Columns manager UI close link HTML
* @type {String}
*/
this.btnCloseHtml = defaultsStr(f.btn_close_html, null);
/**
* Css for columns manager UI close link
* @type {String}
*/
this.btnCloseCssClass = defaultsStr(f.btn_close_css_class,
this.btnCssClass);
/**
* Extension's stylesheet filename
* @type {String}
*/
this.stylesheet = defaultsStr(f.stylesheet, 'colsVisibility.css');
/**
* Css for columns manager UI span
* @type {String}
*/
this.spanCssClass = defaultsStr(f.span_css_class, 'colVisSpan');
/**
* Css for columns manager UI main container
* @type {String}
*/
this.contCssClass = defaultsStr(f.cont_css_class, 'colVisCont');
/**
* Css for columns manager UI checklist (ul)
* @type {String}
*/
this.listCssClass = defaultsStr(cfg.list_css_class, 'cols_checklist');
/**
* Css for columns manager UI checklist item (li)
* @type {String}
*/
this.listItemCssClass = defaultsStr(cfg.checklist_item_css_class,
'cols_checklist_item');
/**
* Css for columns manager UI checklist item selected state (li)
* @type {String}
*/
this.listSlcItemCssClass = defaultsStr(
cfg.checklist_selected_item_css_class,
'cols_checklist_slc_item'
);
/**
* Text preceding the columns list, defaults to 'Hide' or 'Show'
* depending on tick mode (tick_to_hide option)
* @type {String}
*/
this.text = defaultsStr(f.text, this.tickToHide ? 'Hide: ' : 'Show: ');
/**
* List of columns indexes to be hidden at initialization
* @type {Array}
*/
this.atStart = defaultsArr(f.at_start, []);
/**
* Enable hover behaviour on columns manager button/link
* @type {Boolean}
*/
//tick to hide or show column
this.tickToHide = f.tick_to_hide===false ? false : true;
//enables/disables cols manager generation
this.manager = f.manager===false ? false : true;
//only if external headers
this.headersTbl = f.headers_table || false;
//only if external headers
this.headersIndex = f.headers_index || 1;
//id of container element
this.contElTgtId = f.container_target_id || null;
//alternative headers text
this.headersText = f.headers_text || null;
//id of button container element
this.btnTgtId = f.btn_target_id || null;
//defines show/hide cols text
this.btnText = f.btn_text || 'Columns&#9660;';
//defines show/hide cols button innerHtml
this.btnHtml = f.btn_html || null;
//defines css class for show/hide cols button
this.btnCssClass = f.btn_css_class || 'colVis';
//defines close link text
this.btnCloseText = f.btn_close_text || 'Close';
//defines close button innerHtml
this.btnCloseHtml = f.btn_close_html || null;
//defines css class for close button
this.btnCloseCssClass = f.btn_close_css_class || this.btnCssClass;
this.stylesheet = f.stylesheet || 'colsVisibility.css';
//span containing show/hide cols button
this.prfx = 'colVis_';
//defines css class span containing show/hide cols
this.spanCssClass = f.span_css_class || 'colVisSpan';
this.prfxCont = this.prfx + 'Cont_';
//defines css class div containing show/hide cols
this.contCssClass = f.cont_css_class || 'colVisCont';
//defines css class for cols list (ul)
this.listCssClass = cfg.list_css_class ||'cols_checklist';
//defines css class for list item (li)
this.listItemCssClass = cfg.checklist_item_css_class ||
'cols_checklist_item';
//defines css class for selected list item (li)
this.listSlcItemCssClass = cfg.checklist_selected_item_css_class ||
'cols_checklist_slc_item';
//text preceding columns list
this.text = f.text || (this.tickToHide ? 'Hide: ' : 'Show: ');
this.atStart = f.at_start || null;
this.enableHover = Boolean(f.enable_hover);
/**
* Enable select all option, disabled by default
* @type {Boolean}
*/
//enables select all option
this.enableTickAll = Boolean(f.enable_tick_all);
//text preceding columns list
this.tickAllText = f.tick_all_text || 'Select all:';
/**
* Text for select all option, defaults to 'Select all:'
* @type {String}
*/
this.tickAllText = defaultsStr(f.tick_all_text, 'Select all:');
/**
* Default position in toolbar ('left'|'center'|'right')
* @type {String}
*/
this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);
/**
* List of indexes of hidden columns
* @private
*/
//array containing hidden columns indexes
this.hiddenCols = [];
this.tblHasColTag = (Dom.tag(tf.tbl,'col').length > 0);
/**
* Bound mouseup wrapper
* @private
*/
this.boundMouseup = null;
//callback invoked just after cols manager is loaded
this.onLoaded = Types.isFn(f.on_loaded) ? f.on_loaded : null;
//calls function before cols manager is opened
this.onBeforeOpen = Types.isFn(f.on_before_open) ?
f.on_before_open : null;
//calls function after cols manager is opened
this.onAfterOpen = Types.isFn(f.on_after_open) ? f.on_after_open : null;
//calls function before cols manager is closed
this.onBeforeClose = Types.isFn(f.on_before_close) ?
f.on_before_close : null;
//calls function after cols manager is closed
this.onAfterClose = Types.isFn(f.on_after_close) ?
f.on_after_close : null;
/**
* Callback fired when the extension is initialized
* @type {Function}
*/
this.onLoaded = defaultsFn(f.on_loaded, EMPTY_FN);
//callback before col is hidden
this.onBeforeColHidden = Types.isFn(f.on_before_col_hidden) ?
f.on_before_col_hidden : null;
//callback after col is hidden
this.onAfterColHidden = Types.isFn(f.on_after_col_hidden) ?
f.on_after_col_hidden : null;
//callback before col is displayed
this.onBeforeColDisplayed = Types.isFn(f.on_before_col_displayed) ?
f.on_before_col_displayed : null;
//callback after col is displayed
this.onAfterColDisplayed = Types.isFn(f.on_after_col_displayed) ?
f.on_after_col_displayed : null;
/**
* Callback fired before the columns manager is opened
* @type {Function}
*/
this.onBeforeOpen = defaultsFn(f.on_before_open, EMPTY_FN);
/**
* Callback fired after the columns manager is opened
* @type {Function}
*/
this.onAfterOpen = defaultsFn(f.on_after_open, EMPTY_FN);
/**
* Callback fired before the columns manager is closed
* @type {Function}
*/
this.onBeforeClose = defaultsFn(f.on_before_close, EMPTY_FN);
/**
* Callback fired after the columns manager is closed
* @type {Function}
*/
this.onAfterClose = defaultsFn(f.on_after_close, EMPTY_FN);
/**
* Callback fired before a column is hidden
* @type {Function}
*/
this.onBeforeColHidden = defaultsFn(f.on_before_col_hidden, EMPTY_FN);
/**
* Callback fired after a column is hidden
* @type {Function}
*/
this.onAfterColHidden = defaultsFn(f.on_after_col_hidden, EMPTY_FN);
/**
* Callback fired before a column is displayed
* @type {Function}
*/
this.onBeforeColDisplayed = defaultsFn(f.on_before_col_displayed,
EMPTY_FN);
/**
* Callback fired after a column is displayed
* @type {Function}
*/
this.onAfterColDisplayed = defaultsFn(f.on_after_col_displayed,
EMPTY_FN);
//Grid layout support
if (tf.gridLayout) {
//Grid layout compatibility
if(tf.gridLayout){
this.headersTbl = tf.feature('gridLayout').headTbl; //headers table
this.headersIndex = 0; //headers index
this.onAfterColDisplayed = function(){};
this.onAfterColHidden = function(){};
}
//Loads extension stylesheet
tf.import(f.name + 'Style', tf.getStylePath() + this.stylesheet, null,
'link');
tf.import(f.name+'Style', tf.stylePath + this.stylesheet, null, 'link');
this.enable();
this.tf = tf;
}
/**
* Mouse-up event handler handling popup auto-close behaviour
* @private
*/
onMouseup(evt) {
let targetElm = targetEvt(evt);
toggle(){
var contDisplay = this.contEl.style.display;
var onBeforeOpen = this.onBeforeOpen;
var onBeforeClose = this.onBeforeClose;
var onAfterOpen = this.onAfterOpen;
var onAfterClose = this.onAfterClose;
while (targetElm && targetElm !== this.contEl
&& targetElm !== this.btnEl) {
targetElm = targetElm.parentNode;
if(onBeforeOpen && contDisplay !== 'inline'){
onBeforeOpen.call(null, this);
}
if (targetElm !== this.contEl && targetElm !== this.btnEl) {
this.toggle();
}
return;
}
/**
* Toggle columns manager UI
*/
toggle() {
// ensure mouseup event handler is removed
removeEvt(root, 'mouseup', this.boundMouseup);
let contDisplay = this.contEl.style.display;
if (contDisplay !== 'inline') {
this.onBeforeOpen(this);
}
if (contDisplay === 'inline') {
this.onBeforeClose(this);
if(onBeforeClose && contDisplay === 'inline'){
onBeforeClose.call(null, this);
}
this.contEl.style.display = contDisplay === 'inline' ?
NONE : 'inline';
'none' : 'inline';
if (contDisplay !== 'inline') {
this.onAfterOpen(this);
addEvt(root, 'mouseup', this.boundMouseup);
if(onAfterOpen && contDisplay !== 'inline'){
onAfterOpen.call(null, this);
}
if (contDisplay === 'inline') {
this.onAfterClose(this);
if(onAfterClose && contDisplay === 'inline'){
onAfterClose.call(null, this);
}
}
/**
* Check an item in columns manager UI
* @private
*/
checkItem(lbl) {
let li = lbl.parentNode;
if (!li || !lbl) {
checkItem(lbl){
var li = lbl.parentNode;
if(!li || !lbl){
return;
}
let isChecked = lbl.firstChild.checked;
let colIndex = lbl.firstChild.getAttribute('id').split('_')[1];
var isChecked = lbl.firstChild.checked;
var colIndex = lbl.firstChild.getAttribute('id').split('_')[1];
colIndex = parseInt(colIndex, 10);
if (isChecked) {
addClass(li, this.listSlcItemCssClass);
if(isChecked){
Dom.addClass(li, this.listSlcItemCssClass);
} else {
removeClass(li, this.listSlcItemCssClass);
Dom.removeClass(li, this.listSlcItemCssClass);
}
let hide = false;
if ((this.tickToHide && isChecked) ||
(!this.tickToHide && !isChecked)) {
var hide = false;
if((this.tickToHide && isChecked) || (!this.tickToHide && !isChecked)){
hide = true;
}
this.setHidden(colIndex, hide);
}
/**
* Initializes ColsVisibility instance
*/
init() {
if (this.initialized || !this.manager) {
init(){
if(!this.manager){
return;
}
this.emitter.emit('initializing-extension', this,
!isNull(this.btnTgtId));
this.emitter.on(['hide-column'],
(tf, colIndex) => this.hideCol(colIndex));
this.buildBtn();
this.buildManager();
/** @inherited */
this.initialized = true;
this.boundMouseup = this.onMouseup.bind(this);
this.emitter.emit('columns-visibility-initialized', this.tf, this);
this.emitter.emit('extension-initialized', this);
// Hide columns at start at very end of initialization, do not move
// as order is important
this._hideAtStart();
}
/**
* Build main button UI
*/
buildBtn() {
if (this.btnEl) {
buildBtn(){
if(this.btnEl){
return;
}
let tf = this.tf;
let span = createElm('span');
var tf = this.tf;
var span = Dom.create('span', ['id', this.prfx+tf.id]);
span.className = this.spanCssClass;
// Container element (rdiv or custom element)
let targetEl = !this.btnTgtId ?
tf.feature('toolbar').container(this.toolbarPosition) :
elm(this.btnTgtId);
//Container element (rdiv or custom element)
if(!this.btnTgtId){
tf.setToolbar();
}
var targetEl = !this.btnTgtId ? tf.rDiv : Dom.id(this.btnTgtId);
if (!this.btnTgtId) {
let firstChild = targetEl.firstChild;
if(!this.btnTgtId){
var firstChild = targetEl.firstChild;
firstChild.parentNode.insertBefore(span, firstChild);
} else {
targetEl.appendChild(span);
}
if (!this.btnHtml) {
let btn = createElm('a', ['href', 'javascript:;']);
if(!this.btnHtml){
var btn = Dom.create('a', ['href','javascript:;']);
btn.className = this.btnCssClass;
btn.title = this.desc;
btn.innerHTML = this.btnText;
span.appendChild(btn);
if (!this.enableHover) {
addEvt(btn, 'click', (evt) => this.toggle(evt));
if(!this.enableHover){
Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
} else {
addEvt(btn, 'mouseover', (evt) => this.toggle(evt));
Event.add(btn, 'mouseover', (evt)=> { this.toggle(evt); });
}
} else { // Custom html
} else { //Custom html
span.innerHTML = this.btnHtml;
let colVisEl = span.firstChild;
if (!this.enableHover) {
addEvt(colVisEl, 'click', (evt) => this.toggle(evt));
var colVisEl = span.firstChild;
if(!this.enableHover){
Event.add(colVisEl, 'click', (evt)=> { this.toggle(evt); });
} else {
addEvt(colVisEl, 'mouseover', (evt) => this.toggle(evt));
Event.add(colVisEl, 'mouseover', (evt)=> { this.toggle(evt); });
}
}
this.spanEl = span;
this.btnEl = this.spanEl.firstChild;
this.onLoaded(this);
if(this.onLoaded){
this.onLoaded.call(null, this);
}
}
/**
* Build columns manager UI
*/
buildManager() {
let tf = this.tf;
buildManager(){
var tf = this.tf;
let container = !this.contElTgtId ?
createElm('div') :
elm(this.contElTgtId);
var container = !this.contElTgtId ?
Dom.create('div', ['id', this.prfxCont+tf.id]) :
Dom.id(this.contElTgtId);
container.className = this.contCssClass;
//Extension description
let extNameLabel = createElm('p');
var extNameLabel = Dom.create('p');
extNameLabel.innerHTML = this.text;
container.appendChild(extNameLabel);
//Headers list
let ul = createElm('ul');
var ul = Dom.create('ul' ,['id', 'ul'+this.name+'_'+tf.id]);
ul.className = this.listCssClass;
let tbl = this.headersTbl || tf.dom();
let headerIndex = this.headersTbl ?
var tbl = this.headersTbl ? this.headersTbl : tf.tbl;
var headerIndex = this.headersTbl ?
this.headersIndex : tf.getHeadersRowIndex();
let headerRow = tbl.rows[headerIndex];
var headerRow = tbl.rows[headerIndex];
//Tick all option
if (this.enableTickAll) {
let li = createCheckItem('col__' + tf.id, this.tickAllText,
this.tickAllText);
addClass(li, this.listItemCssClass);
if(this.enableTickAll){
var li = Dom.createCheckItem(
'col__'+tf.id, this.tickAllText, this.tickAllText);
Dom.addClass(li, this.listItemCssClass);
ul.appendChild(li);
li.check.checked = !this.tickToHide;
addEvt(li.check, 'click', () => {
for (let h = 0; h < headerRow.cells.length; h++) {
let itm = elm('col_' + h + '_' + tf.id);
if (itm && li.check.checked !== itm.checked) {
Event.add(li.check, 'click', ()=> {
for(var h = 0; h < headerRow.cells.length; h++){
var itm = Dom.id('col_'+h+'_'+tf.id);
if(itm && li.check.checked !== itm.checked){
itm.click();
itm.checked = li.check.checked;
}
@ -493,41 +275,42 @@ export default class ColsVisibility extends Feature {
});
}
for (let i = 0; i < headerRow.cells.length; i++) {
let cell = headerRow.cells[i];
let cellText = this.headersText[i] || this._getHeaderText(cell);
let liElm = createCheckItem('col_' + i + '_' + tf.id, cellText,
cellText);
addClass(liElm, this.listItemCssClass);
if (!this.tickToHide) {
addClass(liElm, this.listSlcItemCssClass);
for(var i = 0; i < headerRow.cells.length; i++){
var cell = headerRow.cells[i];
var cellText = this.headersText && this.headersText[i] ?
this.headersText[i] : this._getHeaderText(cell);
var liElm = Dom.createCheckItem(
'col_'+i+'_'+tf.id, cellText, cellText);
Dom.addClass(liElm, this.listItemCssClass);
if(!this.tickToHide){
Dom.addClass(liElm, this.listSlcItemCssClass);
}
ul.appendChild(liElm);
if (!this.tickToHide) {
if(!this.tickToHide){
liElm.check.checked = true;
}
addEvt(liElm.check, 'click', (evt) => {
let elm = targetEvt(evt);
let lbl = elm.parentNode;
Event.add(liElm.check, 'click', (evt)=> {
var elm = Event.target(evt);
var lbl = elm.parentNode;
this.checkItem(lbl);
});
}
//separator
let p = createElm('p', ['align', 'center']);
let btn;
var p = Dom.create('p', ['align','center']);
var btn;
//Close link
if (!this.btnCloseHtml) {
btn = createElm('a', ['href', 'javascript:;']);
if(!this.btnCloseHtml){
btn = Dom.create('a', ['href','javascript:;']);
btn.className = this.btnCloseCssClass;
btn.innerHTML = this.btnCloseText;
addEvt(btn, 'click', (evt) => this.toggle(evt));
Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
p.appendChild(btn);
} else {
p.innerHTML = this.btnCloseHtml;
btn = p.firstChild;
addEvt(btn, 'click', (evt) => this.toggle(evt));
Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
}
container.appendChild(ul);
@ -535,48 +318,88 @@ export default class ColsVisibility extends Feature {
this.btnEl.parentNode.insertBefore(container, this.btnEl);
this.contEl = container;
if(this.atStart){
var a = this.atStart;
for(var k=0; k<a.length; k++){
var itm = Dom.id('col_'+a[k]+'_'+tf.id);
if(itm){
itm.click();
}
}
}
}
/**
* Hide or show specified columns
* @param {Number} colIndex Column index
* @param {Boolean} hide Hide column if true or show if false
* @param {Numner} colIndex Column index
* @param {Boolean} hide hide column if true or show if false
*/
setHidden(colIndex, hide) {
let tf = this.tf;
let tbl = tf.dom();
setHidden(colIndex, hide){
var tf = this.tf;
var tbl = tf.tbl;
if (hide) {
this.onBeforeColHidden(this, colIndex);
} else {
this.onBeforeColDisplayed(this, colIndex);
if(this.onBeforeColHidden && hide){
this.onBeforeColHidden.call(null, this, colIndex);
}
if(this.onBeforeColDisplayed && !hide){
this.onBeforeColDisplayed.call(null, this, colIndex);
}
this._hideElements(tbl, colIndex, hide);
if (this.headersTbl) {
this._hideElements(this.headersTbl, colIndex, hide);
this._hideCells(tbl, colIndex, hide);
if(this.headersTbl){
this._hideCells(this.headersTbl, colIndex, hide);
}
let hiddenCols = this.hiddenCols;
let itemIndex = hiddenCols.indexOf(colIndex);
if (hide) {
if (itemIndex === -1) {
var hiddenCols = this.hiddenCols;
var itemIndex = hiddenCols.indexOf(colIndex);
if(hide){
if(itemIndex === -1){
this.hiddenCols.push(colIndex);
}
} else {
if (itemIndex !== -1) {
if(itemIndex !== -1){
this.hiddenCols.splice(itemIndex, 1);
}
}
if (hide) {
this.onAfterColHidden(this, colIndex);
this.emitter.emit('column-hidden', tf, this, colIndex,
this.hiddenCols);
} else {
this.onAfterColDisplayed(this, colIndex);
this.emitter.emit('column-shown', tf, this, colIndex,
this.hiddenCols);
var gridLayout;
var headTbl;
var gridColElms;
if(this.onAfterColHidden && hide){
//This event is fired just after a column is displayed for
//grid_layout support
//TODO: grid layout module should be responsible for those
//calculations
if(tf.gridLayout){
gridLayout = tf.feature('gridLayout');
headTbl = gridLayout.headTbl;
gridColElms = gridLayout.gridColElms;
var hiddenWidth = parseInt(
gridColElms[colIndex].style.width, 10);
var headTblW = parseInt(headTbl.style.width, 10);
headTbl.style.width = headTblW - hiddenWidth + 'px';
tbl.style.width = headTbl.style.width;
}
this.onAfterColHidden.call(null, this, colIndex);
}
if(this.onAfterColDisplayed && !hide){
//This event is fired just after a column is displayed for
//grid_layout support
//TODO: grid layout module should be responsible for those
//calculations
if(tf.gridLayout){
gridLayout = tf.feature('gridLayout');
headTbl = gridLayout.headTbl;
gridColElms = gridLayout.gridColElms;
var width = parseInt(gridColElms[colIndex].style.width, 10);
headTbl.style.width =
(parseInt(headTbl.style.width, 10) + width) + 'px';
tf.tbl.style.width = headTbl.style.width;
}
this.onAfterColDisplayed.call(null, this, colIndex);
}
}
@ -584,15 +407,13 @@ export default class ColsVisibility extends Feature {
* Show specified column
* @param {Number} colIndex Column index
*/
showCol(colIndex) {
if (isUndef(colIndex) || !this.isColHidden(colIndex)) {
showCol(colIndex){
if(colIndex === undefined || !this.isColHidden(colIndex)){
return;
}
if (this.manager && this.contEl) {
let itm = elm('col_' + colIndex + '_' + this.tf.id);
if (itm) {
itm.click();
}
if(this.manager && this.contEl){
var itm = Dom.id('col_'+colIndex+'_'+this.tf.id);
if(itm){ itm.click(); }
} else {
this.setHidden(colIndex, false);
}
@ -602,15 +423,13 @@ export default class ColsVisibility extends Feature {
* Hide specified column
* @param {Number} colIndex Column index
*/
hideCol(colIndex) {
if (isUndef(colIndex) || this.isColHidden(colIndex)) {
hideCol(colIndex){
if(colIndex === undefined || this.isColHidden(colIndex)){
return;
}
if (this.manager && this.contEl) {
let itm = elm('col_' + colIndex + '_' + this.tf.id);
if (itm) {
itm.click();
}
if(this.manager && this.contEl){
var itm = Dom.id('col_'+colIndex+'_'+this.tf.id);
if(itm){ itm.click(); }
} else {
this.setHidden(colIndex, true);
}
@ -620,8 +439,8 @@ export default class ColsVisibility extends Feature {
* Determine if specified column is hidden
* @param {Number} colIndex Column index
*/
isColHidden(colIndex) {
if (this.hiddenCols.indexOf(colIndex) !== -1) {
isColHidden(colIndex){
if(this.hiddenCols.indexOf(colIndex) !== -1){
return true;
}
return false;
@ -631,8 +450,8 @@ export default class ColsVisibility extends Feature {
* Toggle visibility of specified column
* @param {Number} colIndex Column index
*/
toggleCol(colIndex) {
if (isUndef(colIndex) || this.isColHidden(colIndex)) {
toggleCol(colIndex){
if(colIndex === undefined || this.isColHidden(colIndex)){
this.showCol(colIndex);
} else {
this.hideCol(colIndex);
@ -640,53 +459,47 @@ export default class ColsVisibility extends Feature {
}
/**
* Return the indexes of the columns currently hidden
* Returns the indexes of the columns currently hidden
* @return {Array} column indexes
*/
getHiddenCols() {
getHiddenCols(){
return this.hiddenCols;
}
/**
* Remove the columns manager
*/
destroy() {
if (!this.initialized) {
destroy(){
if(!this.btnEl && !this.contEl){
return;
}
if (elm(this.contElTgtId)) {
elm(this.contElTgtId).innerHTML = '';
if(Dom.id(this.contElTgtId)){
Dom.id(this.contElTgtId).innerHTML = '';
} else {
this.contEl.innerHTML = '';
removeElm(this.contEl);
Dom.remove(this.contEl);
this.contEl = null;
}
this.btnEl.innerHTML = '';
removeElm(this.btnEl);
Dom.remove(this.btnEl);
this.btnEl = null;
this.emitter.off(['hide-column'],
(tf, colIndex) => this.hideCol(colIndex));
this.boundMouseup = null;
this.initialized = false;
}
_getHeaderText(cell) {
if (!cell.hasChildNodes) {
_getHeaderText(cell){
if(!cell.hasChildNodes){
return '';
}
for (let i = 0; i < cell.childNodes.length; i++) {
let n = cell.childNodes[i];
if (n.nodeType === 3) {
for(var i=0; i<cell.childNodes.length; i++){
var n = cell.childNodes[i];
if(n.nodeType === 3){
return n.nodeValue;
} else if (n.nodeType === 1) {
if (n.id && n.id.indexOf('popUp') !== -1) {
} else if(n.nodeType === 1){
if(n.id && n.id.indexOf('popUp') !== -1){
continue;
} else {
return getText(n);
return Dom.getText(n);
}
}
continue;
@ -694,32 +507,14 @@ export default class ColsVisibility extends Feature {
return '';
}
_hideElements(tbl, colIdx, hide) {
this._hideCells(tbl, colIdx, hide);
this._hideCol(tbl, colIdx, hide);
}
_hideCells(tbl, colIdx, hide) {
for (let i = 0; i < tbl.rows.length; i++) {
let row = tbl.rows[i];
let cell = row.cells[colIdx];
if (cell) {
cell.style.display = hide ? NONE : '';
_hideCells(tbl, colIndex, hide){
for(var i=0; i<tbl.rows.length; i++){
var row = tbl.rows[i];
var cell = row.cells[colIndex];
if(cell){
cell.style.display = hide ? 'none' : '';
}
}
}
_hideCol(tbl, colIdx, hide) {
let colElms = tag(tbl, 'col');
if (colElms.length === 0) {
return;
}
colElms[colIdx].style.display = hide ? NONE : '';
}
_hideAtStart() {
this.atStart.forEach((colIdx) => {
this.hideCol(colIdx);
});
}
}

View file

@ -1,225 +1,110 @@
import {Feature} from '../../feature';
import {createElm, removeElm, elm} from '../../dom';
import {EMPTY_FN, isNull} from '../../types';
import {addEvt} from '../../event';
import {
defaultsBool, defaultsStr, defaultsFn, defaultsNb,
} from '../../settings';
import {RIGHT} from '../../modules/toolbar';
import Dom from '../../dom';
import Types from '../../types';
import Event from '../../event';
/**
* Filters Visibility extension
*/
export default class FiltersVisibility extends Feature {
export default class FiltersVisibility{
/**
* Creates an instance of FiltersVisibility
* @param {TableFilter} tf TableFilter instance
* @param {Object} Configuration object
* Filters Row Visibility extension
* @param {Object} tf TableFilter instance
* @param {Object} f Config
*/
constructor(tf, f) {
super(tf, FiltersVisibility);
constructor(tf, f){
/**
* Module name
* @type {String}
*/
this.initialized = false;
this.name = f.name;
this.desc = f.description || 'Filters row visibility manager';
/**
* Module description
* @type {String}
*/
this.desc = defaultsStr(f.description,
'Filters row visibility manager');
// Path and image filenames
this.stylesheet = f.stylesheet || 'filtersVisibility.css';
this.icnExpand = f.expand_icon_name || 'icn_exp.png';
this.icnCollapse = f.collapse_icon_name || 'icn_clp.png';
/**
* Extension's stylesheet filename
* @type {String}
*/
this.stylesheet = defaultsStr(f.stylesheet , 'filtersVisibility.css');
/**
* Expand icon filename
* @type {String}
*/
this.icnExpand = defaultsStr(f.expand_icon_name, 'icn_exp.png');
/**
* Collapse icon filename
* @type {String}
*/
this.icnCollapse = defaultsStr(f.collapse_icon_name, 'icn_clp.png');
/**
* Main container element
* @private
*/
//expand/collapse filters span element
this.contEl = null;
/**
* Button element
* @private
*/
//expand/collapse filters btn element
this.btnEl = null;
/**
* Expand icon HTML
* @private
*/
this.icnExpandHtml = '<img src="' + tf.themesPath + this.icnExpand +
this.icnExpandHtml = '<img src="'+ tf.themesPath + this.icnExpand +
'" alt="Expand filters" >';
/**
* Collapse icon HTML
* @private
*/
this.icnCollapseHtml = '<img src="' + tf.themesPath + this.icnCollapse +
this.icnCollapseHtml = '<img src="'+ tf.themesPath + this.icnCollapse +
'" alt="Collapse filters" >';
/**
* Default text
* @private
*/
this.defaultText = 'Toggle filters';
/**
* ID of main container element
* @type {String}
*/
this.targetId = f.target_id || null;
//id of container element
this.targetId = f.target_id || null;
//enables/disables expand/collapse icon
this.enableIcon = f.enable_icon===false ? false : true;
this.btnText = f.btn_text || '';
/**
* Enable expand/collapse icon, defaults to true
* @type {Boolean}
*/
this.enableIcon = defaultsBool(f.enable_icon, true);
/**
* Custom text for button
* @type {String}
*/
this.btnText = defaultsStr(f.btn_text, '');
/**
* Collapse button HTML
* @private
*/
//defines expand/collapse filters text
this.collapseBtnHtml = this.enableIcon ?
this.icnCollapseHtml + this.btnText :
this.btnText || this.defaultText;
/**
* Expand button HTML
* @private
*/
this.expandBtnHtml = this.enableIcon ?
this.expandBtnHtml = this.enableIcon ?
this.icnExpandHtml + this.btnText :
this.btnText || this.defaultText;
/**
* Button's custom HTML
* @type {String}
*/
this.btnHtml = defaultsStr(f.btn_html, null);
//defines expand/collapse filters button innerHtml
this.btnHtml = f.btn_html || null;
//defines css class for expand/collapse filters button
this.btnCssClass = f.btn_css_class || 'btnExpClpFlt';
//defines css class span containing expand/collapse filters
this.contCssClass = f.cont_css_class || 'expClpFlt';
this.filtersRowIndex = !Types.isUndef(f.filters_row_index) ?
f.filters_row_index : tf.getFiltersRowIndex();
/**
* Css class for expand/collapse filters button
* @type {String}
*/
this.btnCssClass = defaultsStr(f.btn_css_class, 'btnExpClpFlt');
this.visibleAtStart = !Types.isUndef(f.visible_at_start) ?
Boolean(f.visible_at_start) : true;
/**
* Css class for main container
* @type {String}
*/
this.contCssClass = defaultsStr(f.cont_css_class, 'expClpFlt');
// Prefix
this.prfx = 'fltsVis_';
/**
* Filters row index
* @type {Number}
*/
this.filtersRowIndex = defaultsNb(f.filters_row_index,
tf.getFiltersRowIndex());
//callback before filters row is shown
this.onBeforeShow = Types.isFn(f.on_before_show) ?
f.on_before_show : null;
//callback after filters row is shown
this.onAfterShow = Types.isFn(f.on_after_show) ?
f.on_after_show : null;
//callback before filters row is hidden
this.onBeforeHide = Types.isFn(f.on_before_hide) ?
f.on_before_hide : null;
//callback after filters row is hidden
this.onAfterHide = Types.isFn(f.on_after_hide) ? f.on_after_hide : null;
/**
* Make filters visible at initialization, defaults to true
* @type {Boolean}
*/
this.visibleAtStart = defaultsNb(f.visible_at_start, true);
//Loads extension stylesheet
tf.import(f.name+'Style', tf.stylePath + this.stylesheet, null, 'link');
/**
* Default position in toolbar ('left'|'center'|'right')
* @type {String}
*/
this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);
/**
* Callback fired before filters row is shown
* @type {Function}
*/
this.onBeforeShow = defaultsFn(f.on_before_show, EMPTY_FN);
/**
* Callback fired after filters row is shown
* @type {Function}
*/
this.onAfterShow = defaultsFn(f.on_after_show, EMPTY_FN);
/**
* Callback fired before filters row is hidden
* @type {Function}
*/
this.onBeforeHide = defaultsFn(f.on_before_hide, EMPTY_FN);
/**
* Callback fired after filters row is hidden
* @type {Function}
*/
this.onAfterHide = defaultsFn(f.on_after_hide, EMPTY_FN);
//Import extension's stylesheet
tf.import(f.name + 'Style', tf.getStylePath() + this.stylesheet, null,
'link');
this.enable();
this.tf = tf;
}
/**
* Initialise extension
*/
init() {
if (this.initialized) {
init(){
if(this.initialized){
return;
}
this.emitter.emit('initializing-extension', this,
!isNull(this.targetId));
this.buildUI();
/** @inherited */
this.initialized = true;
this.emitter.on(['show-filters'], (tf, visible) => this.show(visible));
this.emitter.emit('filters-visibility-initialized', this.tf, this);
this.emitter.emit('extension-initialized', this);
}
/**
* Build UI elements
*/
buildUI() {
buildUI(){
let tf = this.tf;
let span = createElm('span');
let span = Dom.create('span',['id', this.prfx+tf.id]);
span.className = this.contCssClass;
// Container element (rdiv or custom element)
let targetEl = !this.targetId ?
tf.feature('toolbar').container(this.toolbarPosition) :
elm(this.targetId);
//Container element (rdiv or custom element)
if(!this.targetId){
tf.setToolbar();
}
let targetEl = !this.targetId ? tf.rDiv : Dom.id(this.targetId);
if (!this.targetId) {
if(!this.targetId){
let firstChild = targetEl.firstChild;
firstChild.parentNode.insertBefore(span, firstChild);
} else {
@ -227,23 +112,23 @@ export default class FiltersVisibility extends Feature {
}
let btn;
if (!this.btnHtml) {
btn = createElm('a', ['href', 'javascript:void(0);']);
if(!this.btnHtml){
btn = Dom.create('a', ['href', 'javascript:void(0);']);
btn.className = this.btnCssClass;
btn.title = this.btnText || this.defaultText;
btn.innerHTML = this.collapseBtnHtml;
span.appendChild(btn);
} else { // Custom html
} else { //Custom html
span.innerHTML = this.btnHtml;
btn = span.firstChild;
}
addEvt(btn, 'click', () => this.toggle());
Event.add(btn, 'click', ()=> this.toggle());
this.contEl = span;
this.btnEl = btn;
if (!this.visibleAtStart) {
if(!this.visibleAtStart){
this.toggle();
}
}
@ -251,64 +136,47 @@ export default class FiltersVisibility extends Feature {
/**
* Toggle filters visibility
*/
toggle() {
toggle(){
let tf = this.tf;
let tbl = tf.gridLayout ? tf.feature('gridLayout').headTbl : tf.dom();
let tbl = tf.gridLayout? tf.feature('gridLayout').headTbl : tf.tbl;
let fltRow = tbl.rows[this.filtersRowIndex];
let isDisplayed = fltRow.style.display === '';
let fltRowDisplay = fltRow.style.display;
this.show(!isDisplayed);
}
/**
* Show or hide filters
*
* @param {boolean} [visible=true] Visibility flag
*/
show(visible = true) {
let tf = this.tf;
let tbl = tf.gridLayout ? tf.feature('gridLayout').headTbl : tf.dom();
let fltRow = tbl.rows[this.filtersRowIndex];
if (visible) {
this.onBeforeShow(this);
if(this.onBeforeShow && fltRowDisplay !== ''){
this.onBeforeShow.call(this, this);
}
if (!visible) {
this.onBeforeHide(this);
if(this.onBeforeHide && fltRowDisplay === ''){
this.onBeforeHide.call(null, this);
}
fltRow.style.display = visible ? '' : 'none';
if (this.enableIcon && !this.btnHtml) {
this.btnEl.innerHTML = visible ?
this.collapseBtnHtml : this.expandBtnHtml;
fltRow.style.display = fltRowDisplay==='' ? 'none' : '';
if(this.enableIcon && !this.btnHtml){
this.btnEl.innerHTML = fltRowDisplay === '' ?
this.expandBtnHtml : this.collapseBtnHtml;
}
if (visible) {
this.onAfterShow(this);
if(this.onAfterShow && fltRowDisplay !== ''){
this.onAfterShow.call(null, this);
}
if (!visible) {
this.onAfterHide(this);
if(this.onAfterHide && fltRowDisplay === ''){
this.onAfterHide.call(null, this);
}
this.emitter.emit('filters-toggled', tf, this, visible);
}
/**
* Destroy the UI
*/
destroy() {
if (!this.initialized) {
destroy(){
if(!this.btnEl && !this.contEl){
return;
}
this.emitter.off(['show-filters'], (tf, visible) => this.show(visible));
this.btnEl.innerHTML = '';
removeElm(this.btnEl);
Dom.remove(this.btnEl);
this.btnEl = null;
this.contEl.innerHTML = '';
removeElm(this.contEl);
Dom.remove(this.contEl);
this.contEl = null;
this.initialized = false;
}

View file

@ -1,176 +1,93 @@
import {Feature} from '../../feature';
import {isUndef, isObj, EMPTY_FN} from '../../types';
import {createElm, elm, tag} from '../../dom';
import {addEvt, bound} from '../../event';
import {parse as parseNb} from '../../number';
import {
NONE, CELL_TAG, HEADER_TAG, STRING, NUMBER, DATE, FORMATTED_NUMBER,
IP_ADDRESS
} from '../../const';
import {defaultsStr, defaultsFn, defaultsArr} from '../../settings';
import Types from '../../types';
import Dom from '../../dom';
import Event from '../../event';
import DateHelper from '../../date';
import Helpers from '../../helpers';
/**
* SortableTable Adapter module
*/
export default class AdapterSortableTable extends Feature {
export default class AdapterSortableTable{
/**
* Creates an instance of AdapterSortableTable
* @param {TableFilter} tf TableFilter instance
* @param {Object} opts Configuration object
* SortableTable Adapter module
* @param {Object} tf TableFilter instance
*/
constructor(tf, opts) {
super(tf, AdapterSortableTable);
/**
* Module name
* @type {String}
*/
constructor(tf, opts){
this.initialized = false;
this.name = opts.name;
this.desc = opts.description || 'Sortable table';
/**
* Module description
* @type {String}
*/
this.desc = defaultsStr(opts.description, 'Sortable table');
/**
* Indicate whether table previously sorted
* @type {Boolean}
* @private
*/
//indicates if tables was sorted
this.sorted = false;
/**
* List of sort type per column basis
* @type {Array}
*/
this.sortTypes = defaultsArr(opts.types, tf.colTypes);
/**
* Column to be sorted at initialization, ie:
* sort_col_at_start: [1, true]
* @type {Array}
*/
this.sortColAtStart = defaultsArr(opts.sort_col_at_start, null);
/**
* Enable asynchronous sort, if triggers are external
* @type {Boolean}
*/
this.sortTypes = Types.isArray(opts.types) ? opts.types : [];
this.sortColAtStart = Types.isArray(opts.sort_col_at_start) ?
opts.sort_col_at_start : null;
this.asyncSort = Boolean(opts.async_sort);
/**
* List of element IDs triggering sort on a per column basis
* @type {Array}
*/
this.triggerIds = defaultsArr(opts.trigger_ids, []);
this.triggerIds = Types.isArray(opts.trigger_ids) ?
opts.trigger_ids : [];
// edit .sort-arrow.descending / .sort-arrow.ascending in
// tablefilter.css to reflect any path change
/**
* Path to images
* @type {String}
*/
this.imgPath = defaultsStr(opts.images_path, tf.themesPath);
this.imgPath = opts.images_path || tf.themesPath;
this.imgBlank = opts.image_blank || 'blank.png';
this.imgClassName = opts.image_class_name || 'sort-arrow';
this.imgAscClassName = opts.image_asc_class_name || 'ascending';
this.imgDescClassName = opts.image_desc_class_name ||'descending';
//cell attribute storing custom key
this.customKey = opts.custom_key || 'data-tf-sortKey';
/**
* Blank image file name
* @type {String}
*/
this.imgBlank = defaultsStr(opts.image_blank, 'blank.png');
// callback invoked after sort is loaded and instanciated
this.onSortLoaded = Types.isFn(opts.on_sort_loaded) ?
opts.on_sort_loaded : null;
// callback invoked before table is sorted
this.onBeforeSort = Types.isFn(opts.on_before_sort) ?
opts.on_before_sort : null;
// callback invoked after table is sorted
this.onAfterSort = Types.isFn(opts.on_after_sort) ?
opts.on_after_sort : null;
/**
* Css class for sort indicator image
* @type {String}
*/
this.imgClassName = defaultsStr(opts.image_class_name, 'sort-arrow');
/**
* Css class for ascending sort indicator image
* @type {String}
*/
this.imgAscClassName = defaultsStr(opts.image_asc_class_name,
'ascending');
/**
* Css class for descending sort indicator image
* @type {String}
*/
this.imgDescClassName = defaultsStr(opts.image_desc_class_name,
'descending');
/**
* Cell attribute key storing custom value used for sorting
* @type {String}
*/
this.customKey = defaultsStr(opts.custom_key, 'data-tf-sortKey');
/**
* Callback fired when sort extension is instanciated
* @type {Function}
*/
this.onSortLoaded = defaultsFn(opts.on_sort_loaded, EMPTY_FN);
/**
* Callback fired before a table column is sorted
* @type {Function}
*/
this.onBeforeSort = defaultsFn(opts.on_before_sort, EMPTY_FN);
/**
* Callback fired after a table column is sorted
* @type {Function}
*/
this.onAfterSort = defaultsFn(opts.on_after_sort, EMPTY_FN);
/**
* SortableTable instance
* @private
*/
this.stt = null;
this.enable();
this.tf = tf;
this.emitter = tf.emitter;
}
/**
* Initializes AdapterSortableTable instance
*/
init() {
if (this.initialized) {
return;
}
init(){
let tf = this.tf;
let adpt = this;
// SortableTable class sanity check (sortabletable.js)
if (isUndef(SortableTable)) {
if(Types.isUndef(SortableTable)){
throw new Error('SortableTable class not found.');
}
// Add any date format if needed
this.emitter.emit('add-date-type-formats', this.tf, this.sortTypes);
this.overrideSortableTable();
this.setSortTypes();
this.onSortLoaded(tf, this);
//Column sort at start
let sortColAtStart = adpt.sortColAtStart;
if(sortColAtStart){
this.stt.sort(sortColAtStart[0], sortColAtStart[1]);
}
if(this.onSortLoaded){
this.onSortLoaded.call(null, tf, this);
}
/*** SortableTable callbacks ***/
this.stt.onbeforesort = function () {
adpt.onBeforeSort(tf, adpt.stt.sortColumn);
this.stt.onbeforesort = function(){
if(adpt.onBeforeSort){
adpt.onBeforeSort.call(null, tf, adpt.stt.sortColumn);
}
/*** sort behaviour for paging ***/
if (tf.paging) {
if(tf.paging){
tf.feature('paging').disable();
}
};
this.stt.onsort = function () {
this.stt.onsort = function(){
adpt.sorted = true;
//sort behaviour for paging
if (tf.paging) {
if(tf.paging){
let paginator = tf.feature('paging');
// recalculate valid rows index as sorting may have change it
tf.getValidRows(true);
@ -178,23 +95,14 @@ export default class AdapterSortableTable extends Feature {
paginator.setPage(paginator.getPage());
}
adpt.onAfterSort(tf, adpt.stt.sortColumn, adpt.stt.descending);
adpt.emitter.emit('column-sorted', tf, adpt.stt.sortColumn,
adpt.stt.descending);
if(adpt.onAfterSort){
adpt.onAfterSort.call(null, tf, adpt.stt.sortColumn);
}
adpt.emitter.emit('column-sorted', tf, adpt.stt.sortColumn);
};
// Column sort at start
let sortColAtStart = adpt.sortColAtStart;
if (sortColAtStart) {
this.stt.sort(sortColAtStart[0], sortColAtStart[1]);
}
this.emitter.on(['sort'], bound(this.sortByColumnIndexHandler, this));
/** @inherited */
this.initialized = true;
this.emitter.emit('sort-initialized', tf, this);
}
/**
@ -202,19 +110,11 @@ export default class AdapterSortableTable extends Feature {
* @param {Number} colIdx Column index
* @param {Boolean} desc Optional: descending manner
*/
sortByColumnIndex(colIdx, desc) {
sortByColumnIndex(colIdx, desc){
this.stt.sort(colIdx, desc);
}
/** @private */
sortByColumnIndexHandler(tf, colIdx, desc) {
this.sortByColumnIndex(colIdx, desc);
}
/**
* Set SortableTable overrides for TableFilter integration
*/
overrideSortableTable() {
overrideSortableTable(){
let adpt = this,
tf = this.tf;
@ -222,15 +122,15 @@ export default class AdapterSortableTable extends Feature {
* Overrides headerOnclick method in order to handle th event
* @param {Object} e [description]
*/
SortableTable.prototype.headerOnclick = function (evt) {
if (!adpt.initialized) {
SortableTable.prototype.headerOnclick = function(evt){
if(!adpt.initialized){
return;
}
// find Header element
let el = evt.target || evt.srcElement;
while (el.tagName !== CELL_TAG && el.tagName !== HEADER_TAG) {
while(el.tagName !== 'TD' && el.tagName !== 'TH'){
el = el.parentNode;
}
@ -246,10 +146,10 @@ export default class AdapterSortableTable extends Feature {
* @param {Object} oTd TD element
* @return {Number} Cell index
*/
SortableTable.getCellIndex = function (oTd) {
SortableTable.getCellIndex = function(oTd){
let cells = oTd.parentNode.cells,
l = cells.length, i;
for (i = 0; cells[i] !== oTd && i < l; i++) { }
for (i = 0; cells[i] != oTd && i < l; i++){}
return i;
};
@ -257,10 +157,10 @@ export default class AdapterSortableTable extends Feature {
* Overrides initHeader in order to handle filters row position
* @param {Array} oSortTypes
*/
SortableTable.prototype.initHeader = function (oSortTypes) {
SortableTable.prototype.initHeader = function(oSortTypes){
let stt = this;
if (!stt.tHead) {
if (tf.gridLayout) {
if (!stt.tHead){
if(tf.gridLayout){
stt.tHead = tf.feature('gridLayout').headTbl.tHead;
} else {
return;
@ -275,15 +175,15 @@ export default class AdapterSortableTable extends Feature {
for (let i = 0; i < l; i++) {
c = cells[i];
if (stt.sortTypes[i] !== null && stt.sortTypes[i] !== 'None') {
if (stt.sortTypes[i] !== null && stt.sortTypes[i] !== 'None'){
c.style.cursor = 'pointer';
img = createElm('img',
img = Dom.create('img',
['src', adpt.imgPath + adpt.imgBlank]);
c.appendChild(img);
if (stt.sortTypes[i] !== null) {
c.setAttribute('_sortType', stt.sortTypes[i]);
if (stt.sortTypes[i] !== null){
c.setAttribute( '_sortType', stt.sortTypes[i]);
}
addEvt(c, 'click', stt._headerOnclick);
Event.add(c, 'click', stt._headerOnclick);
} else {
c.setAttribute('_sortType', oSortTypes[i]);
c._sortType = 'None';
@ -295,44 +195,40 @@ export default class AdapterSortableTable extends Feature {
/**
* Overrides updateHeaderArrows in order to handle arrows indicators
*/
SortableTable.prototype.updateHeaderArrows = function () {
SortableTable.prototype.updateHeaderArrows = function(){
let stt = this;
let cells, l, img;
// external headers
if (adpt.asyncSort && adpt.triggerIds.length > 0) {
if(adpt.asyncSort && adpt.triggerIds.length > 0){
let triggers = adpt.triggerIds;
cells = [];
l = triggers.length;
for (let j = 0; j < l; j++) {
cells.push(elm(triggers[j]));
for(let j=0; j<triggers.length; j++){
cells.push(Dom.id(triggers[j]));
}
} else {
if (!this.tHead) {
if(!this.tHead){
return;
}
cells = stt.tHead.rows[stt.headersRow].cells;
l = cells.length;
}
for (let i = 0; i < l; i++) {
let cell = cells[i];
if (!cell) {
continue;
}
let cellAttr = cell.getAttribute('_sortType');
if (cellAttr !== null && cellAttr !== 'None') {
img = cell.lastChild || cell;
if (img.nodeName.toLowerCase() !== 'img') {
img = createElm('img',
for(let i = 0; i < l; i++){
let cellAttr = cells[i].getAttribute('_sortType');
if(cellAttr !== null && cellAttr !== 'None'){
img = cells[i].lastChild || cells[i];
if(img.nodeName.toLowerCase() !== 'img'){
img = Dom.create('img',
['src', adpt.imgPath + adpt.imgBlank]);
cell.appendChild(img);
cells[i].appendChild(img);
}
if (i === stt.sortColumn) {
img.className = adpt.imgClassName + ' ' +
if (i === stt.sortColumn){
img.className = adpt.imgClassName +' '+
(this.descending ?
adpt.imgDescClassName :
adpt.imgAscClassName);
} else {
} else{
img.className = adpt.imgClassName;
}
}
@ -346,11 +242,11 @@ export default class AdapterSortableTable extends Feature {
* @param {Number} nColumn
* @return {String}
*/
SortableTable.prototype.getRowValue = function (oRow, sType, nColumn) {
SortableTable.prototype.getRowValue = function(oRow, sType, nColumn){
let stt = this;
// if we have defined a custom getRowValue use that
let sortTypeInfo = stt._sortTypeInfo[sType];
if (sortTypeInfo && sortTypeInfo.getRowValue) {
if (sortTypeInfo && sortTypeInfo.getRowValue){
return sortTypeInfo.getRowValue(oRow, nColumn);
}
let c = oRow.cells[nColumn];
@ -361,94 +257,82 @@ export default class AdapterSortableTable extends Feature {
/**
* Overrides getInnerText in order to avoid Firefox unexpected sorting
* behaviour with untrimmed text elements
* @param {Object} cell DOM element
* @param {Object} oNode DOM element
* @return {String} DOM element inner text
*/
SortableTable.getInnerText = function (cell) {
if (!cell) {
SortableTable.getInnerText = function(oNode){
if(!oNode){
return;
}
if (cell.getAttribute(adpt.customKey)) {
return cell.getAttribute(adpt.customKey);
if(oNode.getAttribute(adpt.customKey)){
return oNode.getAttribute(adpt.customKey);
} else {
return tf.getCellValue(cell);
return Dom.getText(oNode);
}
};
}
/**
* Adds a sort type
*/
addSortType(...args) {
// Extract the arguments
let [id, caster, sorter, getRowValue] = args;
SortableTable.prototype.addSortType(id, caster, sorter, getRowValue);
addSortType(){
var args = arguments;
SortableTable.prototype.addSortType(args[0], args[1], args[2], args[3]);
}
/**
* Sets the sort types on a column basis
* @private
*/
setSortTypes() {
setSortTypes(){
let tf = this.tf,
sortTypes = this.sortTypes,
_sortTypes = [];
tf.eachCol((i) => {
for(let i=0; i<tf.nbCells; i++){
let colType;
if (sortTypes[i]) {
colType = sortTypes[i];
if (isObj(colType)) {
if (colType.type === DATE) {
colType = this._addDateType(i, sortTypes);
}
else if (colType.type === FORMATTED_NUMBER) {
let decimal = colType.decimal || tf.decimalSeparator;
colType = this._addNumberType(i, decimal);
}
} else {
colType = colType.toLowerCase();
if (colType === DATE) {
colType = this._addDateType(i, sortTypes);
}
else if (colType === FORMATTED_NUMBER ||
colType === NUMBER) {
colType = this._addNumberType(i, tf.decimalSeparator);
}
else if (colType === NONE) {
// TODO: normalise 'none' vs 'None'
colType = 'None';
}
if(sortTypes[i]){
colType = sortTypes[i].toLowerCase();
if(colType === 'none'){
colType = 'None';
}
} else { // resolve column types
if(tf.hasColNbFormat && tf.colNbFormat[i] !== null){
colType = tf.colNbFormat[i].toLowerCase();
} else if(tf.hasColDateType && tf.colDateType[i] !== null){
colType = tf.colDateType[i].toLowerCase()+'date';
} else {
colType = 'String';
}
} else {
colType = STRING;
}
_sortTypes.push(colType);
});
}
//Public TF method to add sort type
//Custom sort types
this.addSortType('number', Number);
this.addSortType('caseinsensitivestring', SortableTable.toUpperCase);
this.addSortType(STRING);
this.addSortType(IP_ADDRESS, ipAddress, sortIP);
this.addSortType('date', SortableTable.toDate);
this.addSortType('string');
this.addSortType('us', usNumberConverter);
this.addSortType('eu', euNumberConverter);
this.addSortType('dmydate', dmyDateConverter );
this.addSortType('ymddate', ymdDateConverter);
this.addSortType('mdydate', mdyDateConverter);
this.addSortType('ddmmmyyyydate', ddmmmyyyyDateConverter);
this.addSortType('ipaddress', ipAddress, sortIP);
this.stt = new SortableTable(tf.dom(), _sortTypes);
this.stt = new SortableTable(tf.tbl, _sortTypes);
/*** external table headers adapter ***/
if (this.asyncSort && this.triggerIds.length > 0) {
if(this.asyncSort && this.triggerIds.length > 0){
let triggers = this.triggerIds;
for (let j = 0; j < triggers.length; j++) {
if (triggers[j] === null) {
for(let j=0; j<triggers.length; j++){
if(triggers[j] === null){
continue;
}
let trigger = elm(triggers[j]);
if (trigger) {
let trigger = Dom.id(triggers[j]);
if(trigger){
trigger.style.cursor = 'pointer';
addEvt(trigger, 'click', (evt) => {
Event.add(trigger, 'click', (evt) => {
let elm = evt.target;
if (!this.tf.sort) {
if(!this.tf.sort){
return;
}
this.stt.asyncSort(triggers.indexOf(elm.id));
@ -459,77 +343,69 @@ export default class AdapterSortableTable extends Feature {
}
}
_addDateType(colIndex, types) {
let tf = this.tf;
let dateType = tf.feature('dateType');
let locale = dateType.getOptions(colIndex, types).locale || tf.locale;
let colType = `${DATE}-${locale}`;
this.addSortType(colType, (value) => {
let parsedDate = dateType.parse(value, locale);
// Invalid date defaults to Wed Feb 04 -768 11:00:00
return isNaN(+parsedDate) ? new Date(-86400000000000) : parsedDate;
});
return colType;
}
_addNumberType(colIndex, decimal) {
let colType = `${FORMATTED_NUMBER}${decimal === '.' ? '' : '-custom'}`;
this.addSortType(colType, (value) => {
return parseNb(value, decimal);
});
return colType;
}
/**
* Remove extension
* Destroy sort
*/
destroy() {
if (!this.initialized) {
return;
}
destroy(){
let tf = this.tf;
this.emitter.off(['sort'], bound(this.sortByColumnIndexHandler, this));
this.sorted = false;
this.initialized = false;
this.stt.destroy();
let ids = tf.getFiltersId();
for (let idx = 0; idx < ids.length; idx++) {
for (let idx = 0; idx < ids.length; idx++){
let header = tf.getHeaderElement(idx);
let img = tag(header, 'img');
let img = Dom.tag(header, 'img');
if (img.length === 1) {
if(img.length === 1){
header.removeChild(img[0]);
}
}
this.initialized = false;
}
}
AdapterSortableTable.meta = {altName: 'sort'};
//Converters
function ipAddress(value) {
function usNumberConverter(s){
return Helpers.removeNbFormat(s, 'us');
}
function euNumberConverter(s){
return Helpers.removeNbFormat(s, 'eu');
}
function dateConverter(s, format){
return DateHelper.format(s, format);
}
function dmyDateConverter(s){
return dateConverter(s, 'DMY');
}
function mdyDateConverter(s){
return dateConverter(s, 'MDY');
}
function ymdDateConverter(s){
return dateConverter(s, 'YMD');
}
function ddmmmyyyyDateConverter(s){
return dateConverter(s, 'DDMMMYYYY');
}
function ipAddress(value){
let vals = value.split('.');
// eslint-disable-next-line no-unused-vars
for (let x in vals) {
let val = vals[x];
while (3 > val.length) {
val = '0' + val;
while (3 > val.length){
val = '0'+val;
}
vals[x] = val;
}
return vals.join('.');
}
function sortIP(a, b) {
function sortIP(a,b){
let aa = ipAddress(a.value.toLowerCase());
let bb = ipAddress(b.value.toLowerCase());
if (aa === bb) {
if (aa==bb){
return 0;
} else if (aa < bb) {
} else if (aa<bb){
return -1;
} else {
return 1;

View file

@ -1,8 +1,8 @@
// import 'script!sortabletable';
import AdapterSortableTable from './adapterSortabletable';
import {root} from '../../root';
if (!root.SortableTable) {
require('script-loader!sortabletable');
if(!window.SortableTable){
require('script!sortabletable');
}
export default AdapterSortableTable;

View file

@ -1,102 +0,0 @@
import {toCamelCase} from './string';
const NOT_IMPLEMENTED = 'Not implemented.';
/**
* Base class defining the interface of a TableFilter feature
*/
export class Feature {
/**
* Creates an instance of Feature
* @param {Object} tf TableFilter instance
* @param {Class} feature Feature class for TableFilter registration
*/
constructor(tf, cls) {
cls.meta = cls.meta || {};
/**
* TableFilter instance
* @type {TableFilter}
*/
this.tf = tf;
/**
* Feature name is the camelised class name as per TableFilter's
* convention
* @type {String}
*/
this.feature = cls.meta.altName || cls.meta.name
|| toCamelCase(cls.name);
/**
* TableFilter feature setting
* @type {Boolean}
*/
this.enabled = tf[this.feature];
/**
* TableFilter configuration
* @type {Object}
*/
this.config = tf.config();
/**
* TableFilter emitter instance
* @type {Emitter}
*/
this.emitter = tf.emitter;
/**
* Field indicating whether Feature is initialized
* @type {Boolean}
*/
this.initialized = false;
/** Subscribe to destroy event */
this.emitter.on(['destroy'], () => this.destroy());
}
/**
* Initialize the feature
*/
init() {
throw new Error(NOT_IMPLEMENTED);
}
/**
* Reset the feature after being disabled
*/
reset() {
this.enable();
this.init();
}
/**
* Destroy the feature
*/
destroy() {
throw new Error(NOT_IMPLEMENTED);
}
/**
* Enable the feature
*/
enable() {
this.enabled = true;
}
/**
* Disable the feature
*/
disable() {
this.enabled = false;
}
/**
* Indicate whether the feature is enabled or not
* @returns {Boolean}
*/
isEnabled() {
return this.enabled === true;
}
}

23
src/helpers.js Normal file
View file

@ -0,0 +1,23 @@
/**
* Misc helpers
*/
import Str from './string';
export default {
removeNbFormat(data, format){
if(!data){
return;
}
if(!format){
format = 'us';
}
let n = data;
if(Str.lower(format) === 'us'){
n =+ n.replace(/[^\d\.-]/g,'');
} else {
n =+ n.replace(/[^\d\,-]/g,'').replace(',','.');
}
return n;
}
};

View file

@ -1,41 +1,27 @@
import {Feature} from '../feature';
import {addClass, removeClass} from '../dom';
import {defaultsStr} from '../settings';
import {bound} from '../event';
import {Feature} from './feature';
import Dom from '../dom';
/**
* Rows with alternating background color for improved readability
*/
export class AlternateRows extends Feature {
/**
* Creates an instance of AlternateRows.
*
* Alternating rows color
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, AlternateRows);
super(tf, 'alternateRows');
let config = this.config;
/**
* Css class for even rows (default: 'even')
* @type {String}
*/
this.evenCss = defaultsStr(config.even_row_css_class, 'even');
/**
* Css class for odd rows (default: 'odd')
* @type {String}
*/
this.oddCss = defaultsStr(config.odd_row_css_class, 'odd');
var config = this.config;
//defines css class for even rows
this.evenCss = config.even_row_css_class || 'even';
//defines css class for odd rows
this.oddCss = config.odd_row_css_class || 'odd';
}
/**
* Sets alternating rows color
*/
init() {
if (this.initialized) {
if(this.initialized){
return;
}
@ -43,29 +29,31 @@ export class AlternateRows extends Feature {
// Subscribe to events
this.emitter.on(['row-processed', 'row-paged'],
bound(this.processRowHandler, this));
this.emitter.on(['column-sorted', 'rows-changed'],
bound(this.processAll, this));
(tf, rowIndex, arrIndex, isValid)=>
this.processRow(rowIndex, arrIndex, isValid));
this.emitter.on(['column-sorted'], ()=> this.processAll());
/** @inherited */
this.initialized = true;
}
/**
* Apply background to all valid rows
*/
processAll() {
if (!this.isEnabled()) {
if(!this.isEnabled()){
return;
}
let tf = this.tf;
let validRowsIndex = tf.getValidRows(true);
let indexLen = validRowsIndex.length;
let idx = 0;
var tf = this.tf;
var validRowsIndex = tf.getValidRows(true);
var noValidRowsIndex = validRowsIndex.length === 0;
//1st index
var beginIndex = noValidRowsIndex ? tf.refRow : 0;
// nb indexes
var indexLen = noValidRowsIndex ?
tf.nbFilterableRows+beginIndex :
validRowsIndex.length;
var idx = 0;
//alternates bg color
for (let j = 0; j < indexLen; j++) {
let rowIdx = validRowsIndex[j];
for(var j=beginIndex; j<indexLen; j++){
var rowIdx = noValidRowsIndex ? j : validRowsIndex[j];
this.setRowBg(rowIdx, idx);
idx++;
}
@ -74,11 +62,10 @@ export class AlternateRows extends Feature {
/**
* Set/remove row background based on row validation
* @param {Number} rowIdx Row index
* @param {Number} arrIdx Array index
* @param {Boolean} isValid Valid row flag
*/
processRow(rowIdx, arrIdx, isValid) {
if (isValid) {
if(isValid){
this.setRowBg(rowIdx, arrIdx);
} else {
this.removeRowBg(rowIdx);
@ -90,54 +77,50 @@ export class AlternateRows extends Feature {
* @param {Number} rowIdx Row index
* @param {Number} idx Valid rows collection index needed to calculate bg
* color
* @private
*/
setRowBg(rowIdx, idx) {
if (!this.isEnabled() || isNaN(rowIdx)) {
if(!this.isEnabled() || isNaN(rowIdx)){
return;
}
let rows = this.tf.dom().rows;
let i = isNaN(idx) ? rowIdx : idx;
var rows = this.tf.tbl.rows;
var i = isNaN(idx) ? rowIdx : idx;
this.removeRowBg(rowIdx);
addClass(rows[rowIdx], (i % 2) ? this.evenCss : this.oddCss);
Dom.addClass(
rows[rowIdx],
(i%2) ? this.evenCss : this.oddCss
);
}
/**
* Removes row background color
* @param {Number} idx Row index
* @private
*/
removeRowBg(idx) {
if (isNaN(idx)) {
if(isNaN(idx)){
return;
}
let rows = this.tf.dom().rows;
removeClass(rows[idx], this.oddCss);
removeClass(rows[idx], this.evenCss);
}
/** @private */
processRowHandler(tf, rowIndex, arrIndex, isValid) {
this.processRow(rowIndex, arrIndex, isValid);
var rows = this.tf.tbl.rows;
Dom.removeClass(rows[idx], this.oddCss);
Dom.removeClass(rows[idx], this.evenCss);
}
/**
* Removes all alternating backgrounds
*/
destroy() {
if (!this.initialized) {
if(!this.initialized){
return;
}
let eachRow = this.tf.eachRow(0);
eachRow((row, i) => this.removeRowBg(i));
for(var i=0; i<this.tf.nbRows; i++){
this.removeRowBg(i);
}
// Unsubscribe to events
this.emitter.off(['row-processed', 'row-paged'],
bound(this.processRowHandler, this));
this.emitter.off(['column-sorted', 'rows-changed'],
bound(this.processAll, this));
(tf, rowIndex, arrIndex, isValid)=>
this.processRow(rowIndex, arrIndex, isValid));
this.emitter.off(['column-sorted'], ()=> this.processAll());
this.initialized = false;
}

View file

@ -1,160 +0,0 @@
import {Feature} from '../feature';
import {
ignoreCase, numSortAsc, numSortDesc,
dateSortAsc, dateSortDesc, sortNumberStr, sortDateStr
} from '../sort';
import {isArray, isObj, isEmpty} from '../types';
import {NUMBER, FORMATTED_NUMBER, DATE} from '../const';
/**
* Base class for Dropdown and CheckList UI components
* @export
* @class BaseDropdown
* @extends {Feature}
*/
export class BaseDropdown extends Feature {
/**
* Creates an instance of BaseDropdown
* @param {TableFilter} tf
*/
constructor(tf, cls) {
super(tf, cls);
let f = this.config;
/**
* Filter options custom sorter on a column basis
* @type {Object}
*/
this.customSorter = isObj(f.filter_options_sorter) &&
isArray(f.filter_options_sorter.col) &&
isArray(f.filter_options_sorter.comparer) ?
f.filter_options_sorter :
null;
// TODO: move here all properties shared by Dropdown and CheckList
/**
* Has custom options
* @type {Boolean}
* @private
*/
this.isCustom = false;
/**
* List of options values
* @type {Array}
* @private
*/
this.opts = [];
/**
* List of options texts for custom values
* @type {Array}
* @private
*/
this.optsTxt = [];
/**
* List of options to be excluded from the checklist filter
* @type {Array}
* @private
*/
this.excludedOpts = [];
}
/**
* Sort passed options based on the type of the specified column
* @param {Number} colIndex Column index
* @param {Array} [options=[]] Collection of values
* @return {Array} Sorted values
* @private
*/
sortOptions(colIndex, options = []) {
let {tf} = this;
if (tf.isCustomOptions(colIndex) || !tf.sortSlc ||
(isArray(tf.sortSlc) && tf.sortSlc.indexOf(colIndex) === -1)) {
return options;
}
let { caseSensitive, sortFilterOptionsDesc } = tf;
let isSortDesc = sortFilterOptionsDesc.indexOf(colIndex) !== -1;
let compareFn;
if (this.customSorter &&
this.customSorter.col.indexOf(colIndex) !== -1) {
var idx = this.customSorter.col.indexOf(colIndex);
compareFn = this.customSorter.comparer[idx];
}
else if (tf.hasType(colIndex, [NUMBER, FORMATTED_NUMBER])) {
let decimal = tf.getDecimal(colIndex);
let comparer = isSortDesc ? numSortDesc : numSortAsc;
compareFn = sortNumberStr(comparer, decimal);
}
else if (tf.hasType(colIndex, [DATE])) {
let locale = tf.feature('dateType').getLocale(colIndex);
let comparer = isSortDesc ? dateSortDesc : dateSortAsc;
compareFn = sortDateStr(comparer, locale);
} else { // string
compareFn = caseSensitive ? undefined : ignoreCase;
if (isSortDesc) {
return options.sort(compareFn).reverse();
}
}
return options.sort(compareFn);
}
/**
* Regenerate filters of specified columns and maintain selection if any
* @param {Array} colIndexes Collection of column indexes
* @private
*/
refreshFilters(colIndexes) {
colIndexes.forEach((colIdx) => {
let values = this.getValues(colIdx);
this.build(colIdx, this.tf.linkedFilters);
this.selectOptions(colIdx, values);
});
}
/**
* Check passed row contains a valid linked value
* @param {Number} rowIdx Row index
* @param {Number} activeFilterIdx Current active filter index
* @returns {Boolean}
*/
isValidLinkedValue(rowIdx, activeFilterIdx) {
let tf = this.tf;
if (tf.disableExcludedOptions) {
return true;
}
if (tf.paging) {
if (!isEmpty(activeFilterIdx) && tf.isRowValid(rowIdx)) {
return true;
}
} else {
if (tf.isRowDisplayed(rowIdx)) {
return true;
}
}
return false;
}
/**
* Refresh linked filters to offer only selected options
*/
linkFilters() {
let tf = this.tf;
if (!tf.linkedFilters || !tf.activeFilterId) {
return;
}
this.refreshAll();
}
}

View file

@ -1,136 +1,76 @@
import {BaseDropdown} from './baseDropdown';
import {
addClass, createCheckItem, createText, createElm, elm, removeClass, tag
} from '../dom';
import {has} from '../array';
import {matchCase, trim, rgxEsc} from '../string';
import {addEvt, removeEvt, targetEvt} from '../event';
import {isEmpty} from '../types';
import {CHECKLIST, NONE} from '../const';
import {defaultsStr, defaultsBool} from '../settings';
import {Feature} from './feature';
import Dom from '../dom';
import Arr from '../array';
import Str from '../string';
import Sort from '../sort';
import Event from '../event';
/**
* Checklist filter UI component
* @export
* @class CheckList
* @extends {BaseDropdown}
*/
export class CheckList extends BaseDropdown {
const SORT_ERROR = 'Filter options for column {0} cannot be sorted in ' +
'{1} manner.';
export class CheckList extends Feature{
/**
* Creates an instance of CheckList
* @param {TableFilter} tf TableFilter instance
* Checklist UI component
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, CheckList);
constructor(tf){
super(tf, 'checkList');
let f = this.config;
// Configuration object
let f = tf.config();
/**
* List of container DOM elements
* @type {Array}
*/
this.containers = [];
this.checkListDiv = []; //checklist container div
//defines css class for div containing checklist filter
this.checkListDivCssClass = f.div_checklist_css_class ||
'div_checklist';
//defines css class for checklist filters
this.checkListCssClass = f.checklist_css_class || 'flt_checklist';
//defines css class for checklist item (li)
this.checkListItemCssClass = f.checklist_item_css_class ||
'flt_checklist_item';
//defines css class for selected checklist item (li)
this.checkListSlcItemCssClass = f.checklist_selected_item_css_class ||
'flt_checklist_slc_item';
//Load on demand text
this.activateCheckListTxt = f.activate_checklist_text ||
'Click to load filter data';
//defines css class for checklist filters
this.checkListItemDisabledCssClass =
f.checklist_item_disabled_css_class ||
'flt_checklist_item_disabled';
this.enableCheckListResetFilter =
f.enable_checklist_reset_filter===false ? false : true;
//checklist filter container div
this.prfxCheckListDiv = 'chkdiv_';
/**
* Css class for the container of the checklist filter (div)
* @type {String}
*/
this.containerCssClass = defaultsStr(f.div_checklist_css_class,
'div_checklist');
/**
* Css class for the checklist filter element (ul)
* @type {String}
*/
this.filterCssClass = defaultsStr(f.checklist_css_class,
'flt_checklist');
/**
* Css class for the item of a checklist (li)
* @type {String}
*/
this.itemCssClass = defaultsStr(f.checklist_item_css_class,
'flt_checklist_item');
/**
* Css class for a selected item of a checklist (li)
* @type {String}
*/
this.selectedItemCssClass = defaultsStr(
f.checklist_selected_item_css_class,
'flt_checklist_slc_item'
);
/**
* Text placed in the filter's container when load filter on demand
* feature is enabled
* @type {String}
*/
this.activateText = defaultsStr(
f.activate_checklist_text,
'Click to load filter data'
);
/**
* Css class for a disabled item of a checklist (li)
* @type {String}
*/
this.disabledItemCssClass = defaultsStr(
f.checklist_item_disabled_css_class,
'flt_checklist_item_disabled'
);
/**
* Enable the reset filter option as first item
* @type {Boolean}
*/
this.enableResetOption = defaultsBool(f.enable_checklist_reset_filter,
true);
/**
* Prefix for container element ID
* @type {String}
* @private
*/
this.prfx = 'chkdiv_';
this.isCustom = null;
this.opts = null;
this.optsTxt = null;
this.excludedOpts = null;
}
/**
* Checklist option click event handler
* @param {Event} evt
* @private
*/
optionClick(evt) {
let elm = targetEvt(evt);
onChange(evt){
let elm = evt.target;
let tf = this.tf;
this.emitter.emit('filter-focus', tf, elm);
this.setItemOption(elm);
tf.activeFilterId = elm.getAttribute('id');
tf.activeFlt = Dom.id(tf.activeFilterId);
tf.filter();
}
/**
* Checklist container click event handler for load-on-demand feature
* @param {Event} evt
* @private
*/
onCheckListClick(evt) {
let elm = targetEvt(evt);
if (this.tf.loadFltOnDemand && elm.getAttribute('filled') === '0') {
let ct = elm.getAttribute('ct');
let div = this.containers[ct];
this.build(ct);
removeEvt(div, 'click', (evt) => this.onCheckListClick(evt));
}
optionClick(evt){
this.setCheckListValues(evt.target);
this.onChange(evt);
}
/**
* Refresh all checklist filters
*/
refreshAll() {
let colIdxs = this.tf.getFiltersByType(CHECKLIST, true);
this.refreshFilters(colIdxs);
onCheckListClick(evt){
let elm = Event.target(evt);
if(this.tf.loadFltOnDemand && elm.getAttribute('filled') === '0'){
let ct = elm.getAttribute('ct');
let div = this.checkListDiv[ct];
this.build(ct);
Event.remove(div, 'click', (evt)=> this.onCheckListClick(evt));
}
}
/**
@ -139,147 +79,189 @@ export class CheckList extends BaseDropdown {
* @param {Boolean} isExternal External filter flag
* @param {DOMElement} container Dom element containing the filter
*/
init(colIndex, isExternal, container) {
init(colIndex, isExternal, container){
let tf = this.tf;
let externalFltTgtId = isExternal ?
tf.externalFltIds[colIndex] : null;
tf.externalFltTgtIds[colIndex] : null;
let divCont = createElm('div',
['id', `${this.prfx}${colIndex}_${tf.id}`],
let divCont = Dom.create('div',
['id', this.prfxCheckListDiv+colIndex+'_'+tf.id],
['ct', colIndex], ['filled', '0']);
divCont.className = this.containerCssClass;
divCont.className = this.checkListDivCssClass;
//filter is appended in desired element
if (externalFltTgtId) {
elm(externalFltTgtId).appendChild(divCont);
if(externalFltTgtId){
Dom.id(externalFltTgtId).appendChild(divCont);
tf.externalFltEls.push(divCont);
} else {
container.appendChild(divCont);
}
this.containers[colIndex] = divCont;
tf.fltIds.push(tf.buildFilterId(colIndex));
this.checkListDiv[colIndex] = divCont;
tf.fltIds.push(tf.prfxFlt+colIndex+'_'+tf.id);
if (!tf.loadFltOnDemand) {
if(!tf.loadFltOnDemand){
this.build(colIndex);
} else {
addEvt(divCont, 'click', (evt) => this.onCheckListClick(evt));
divCont.appendChild(createText(this.activateText));
Event.add(divCont, 'click', (evt)=> this.onCheckListClick(evt));
divCont.appendChild(Dom.text(this.activateCheckListTxt));
}
this.emitter.on(
['build-checklist-filter'],
(tf, colIndex, isLinked) => this.build(colIndex, isLinked)
(tf, colIndex, isExternal)=> this.build(colIndex, isExternal)
);
this.emitter.on(
['select-checklist-options'],
(tf, colIndex, values) => this.selectOptions(colIndex, values)
(tf, colIndex, values)=> this.selectOptions(colIndex, values)
);
this.emitter.on(['rows-changed'], () => this.refreshAll());
this.emitter.on(['after-filtering'], () => this.linkFilters());
/** @inherited */
this.initialized = true;
}
/**
* Build checklist UI
* @param {Number} colIndex Column index
* @param {Boolean} isLinked Enable linked filters behaviour
* @param {Boolean} isExternal Render in external container
* @param {String} extFltId External container id
*/
build(colIndex, isLinked = false) {
build(colIndex, isExternal=false, extFltId=null){
let tf = this.tf;
colIndex = Number(colIndex);
colIndex = parseInt(colIndex, 10);
this.emitter.emit('before-populating-filter', tf, colIndex);
/** @inherited */
this.opts = [];
/** @inherited */
this.optsTxt = [];
let flt = this.containers[colIndex];
let ul = createElm('ul',
['id', tf.fltIds[colIndex]],
['colIndex', colIndex]);
ul.className = this.filterCssClass;
let divFltId = this.prfxCheckListDiv+colIndex+'_'+tf.id;
if((!Dom.id(divFltId) && !isExternal) ||
(!Dom.id(extFltId) && isExternal)){
return;
}
let caseSensitive = tf.caseSensitive;
/** @inherited */
let flt = !isExternal ? this.checkListDiv[colIndex] : Dom.id(extFltId);
let ul = Dom.create(
'ul', ['id', tf.fltIds[colIndex]], ['colIndex', colIndex]);
ul.className = this.checkListCssClass;
Event.add(ul, 'change', (evt)=> this.onChange(evt));
let rows = tf.tbl.rows;
this.isCustom = tf.isCustomOptions(colIndex);
let activeFlt;
if(tf.linkedFilters && tf.activeFilterId){
activeFlt = tf.activeFilterId.split('_')[0];
activeFlt = activeFlt.split(tf.prfxFlt)[1];
}
let filteredDataCol = [];
if(tf.linkedFilters && tf.disableExcludedOptions){
this.excludedOpts = [];
}
for(let k=tf.refRow; k<tf.nbRows; k++){
// always visible rows don't need to appear on selects as always
// valid
if(tf.hasVisibleRows && tf.visibleRows.indexOf(k) !== -1){
continue;
}
let cells = rows[k].cells;
let ncells = cells.length;
// checks if row has exact cell #
if(ncells !== tf.nbCells || this.isCustom){
continue;
}
// this loop retrieves cell data
for(let j=0; j<ncells; j++){
// WTF: cyclomatic complexity hell :)
if((colIndex===j && (!tf.linkedFilters ||
(tf.linkedFilters && tf.disableExcludedOptions)))||
(colIndex===j && tf.linkedFilters &&
((rows[k].style.display === '' && !tf.paging) ||
(tf.paging && ((!activeFlt || activeFlt===colIndex )||
(activeFlt!=colIndex &&
tf.validRowsIndex.indexOf(k) != -1)) )))){
let cell_data = tf.getCellData(cells[j]);
//Vary Peter's patch
let cell_string = Str.matchCase(cell_data, tf.matchCase);
// checks if celldata is already in array
if(!Arr.has(this.opts, cell_string, tf.matchCase)){
this.opts.push(cell_data);
}
let filteredCol = filteredDataCol[j];
if(tf.linkedFilters && tf.disableExcludedOptions){
if(!filteredCol){
filteredCol = tf.getFilteredDataCol(j);
}
if(!Arr.has(filteredCol, cell_string, tf.matchCase) &&
!Arr.has(this.excludedOpts,
cell_string, tf.matchCase)){
this.excludedOpts.push(cell_data);
}
}
}
}
}
//Retrieves custom values
if (this.isCustom) {
if(this.isCustom){
let customValues = tf.getCustomOptions(colIndex);
this.opts = customValues[0];
this.optsTxt = customValues[1];
}
let activeIdx;
let activeFilterId = tf.getActiveFilterId();
if (isLinked && activeFilterId) {
activeIdx = tf.getColumnIndexFromFilterId(activeFilterId);
}
let filteredDataCol = [];
if (isLinked && tf.disableExcludedOptions) {
/** @inherited */
this.excludedOpts = [];
}
flt.innerHTML = '';
let eachRow = tf.eachRow();
eachRow(
(row) => {
let cellValue = tf.getCellValue(row.cells[colIndex]);
//Vary Peter's patch
let cellString = matchCase(cellValue, caseSensitive);
// checks if celldata is already in array
if (!has(this.opts, cellString, caseSensitive)) {
this.opts.push(cellValue);
if(tf.sortSlc && !this.isCustom){
if (!tf.matchCase){
this.opts.sort(Sort.ignoreCase);
if(this.excludedOpts){
this.excludedOpts.sort(Sort.ignoreCase);
}
let filteredCol = filteredDataCol[colIndex];
if (isLinked && tf.disableExcludedOptions) {
if (!filteredCol) {
filteredCol = tf.getVisibleColumnValues(colIndex);
}
if (!has(filteredCol, cellString, caseSensitive) &&
!has(this.excludedOpts, cellString, caseSensitive)) {
this.excludedOpts.push(cellValue);
}
}
},
// continue conditions function
(row, k) => {
// excluded rows don't need to appear on selects as always valid
if (tf.excludeRows.indexOf(k) !== -1) {
return true;
}
// checks if row has expected number of cells
if (row.cells.length !== tf.nbCells || this.isCustom) {
return true;
}
if (isLinked && !this.isValidLinkedValue(k, activeIdx)) {
return true;
} else {
this.opts.sort();
if(this.excludedOpts){
this.excludedOpts.sort();
}
}
);
//sort options
this.opts = this.sortOptions(colIndex, this.opts);
if (this.excludedOpts) {
this.excludedOpts = this.sortOptions(colIndex, this.excludedOpts);
}
//asc sort
if(tf.sortNumAsc.indexOf(colIndex) != -1){
try{
this.opts.sort(Sort.numSortAsc);
if(this.excludedOpts){
this.excludedOpts.sort(Sort.numSortAsc);
}
if(this.isCustom){
this.optsTxt.sort(Sort.numSortAsc);
}
} catch(e) {
throw new Error(SORT_ERROR.replace('{0}', colIndex)
.replace('{1}', 'ascending'));
}//in case there are alphanumeric values
}
//desc sort
if(tf.sortNumDesc.indexOf(colIndex) != -1){
try{
this.opts.sort(Sort.numSortDesc);
if(this.excludedOpts){
this.excludedOpts.sort(Sort.numSortDesc);
}
if(this.isCustom){
this.optsTxt.sort(Sort.numSortDesc);
}
} catch(e) {
throw new Error(SORT_ERROR.replace('{0}', colIndex)
.replace('{1}', 'descending'));
}//in case there are alphanumeric values
}
this.addChecks(colIndex, ul);
if (tf.loadFltOnDemand) {
if(tf.loadFltOnDemand){
flt.innerHTML = '';
}
flt.appendChild(ul);
@ -292,35 +274,31 @@ export class CheckList extends BaseDropdown {
* Add checklist options
* @param {Number} colIndex Column index
* @param {Object} ul Ul element
* @private
*/
addChecks(colIndex, ul) {
addChecks(colIndex, ul){
let tf = this.tf;
let chkCt = this.addTChecks(colIndex, ul);
for (let y = 0; y < this.opts.length; y++) {
for(let y=0; y<this.opts.length; y++){
let val = this.opts[y]; //item value
let lbl = this.isCustom ? this.optsTxt[y] : val; //item text
let fltId = tf.fltIds[colIndex];
let lblIdx = y + chkCt;
let li = createCheckItem(`${fltId}_${lblIdx}`, val, lbl,
['data-idx', lblIdx]);
li.className = this.itemCssClass;
if (tf.linkedFilters && tf.disableExcludedOptions &&
has(this.excludedOpts, matchCase(val, tf.caseSensitive),
tf.caseSensitive)) {
addClass(li, this.disabledItemCssClass);
let li = Dom.createCheckItem(
tf.fltIds[colIndex]+'_'+(y+chkCt), val, lbl);
li.className = this.checkListItemCssClass;
if(tf.linkedFilters && tf.disableExcludedOptions &&
Arr.has(this.excludedOpts,
Str.matchCase(val, tf.matchCase), tf.matchCase)){
Dom.addClass(li, this.checkListItemDisabledCssClass);
li.check.disabled = true;
li.disabled = true;
} else {
addEvt(li.check, 'click', evt => this.optionClick(evt));
Event.add(li.check, 'click', (evt)=> this.optionClick(evt));
}
ul.appendChild(li);
if (val === '') {
if(val === ''){
//item is hidden
li.style.display = NONE;
li.style.display = 'none';
}
}
}
@ -329,111 +307,123 @@ export class CheckList extends BaseDropdown {
* Add checklist header option
* @param {Number} colIndex Column index
* @param {Object} ul Ul element
* @private
*/
addTChecks(colIndex, ul) {
addTChecks(colIndex, ul){
let tf = this.tf;
let chkCt = 1;
let fltId = tf.fltIds[colIndex];
let li0 = createCheckItem(`${fltId}_0`, '',
tf.getClearFilterText(colIndex), ['data-idx', 0]);
li0.className = this.itemCssClass;
let li0 = Dom.createCheckItem(
tf.fltIds[colIndex]+'_0', '', tf.displayAllText);
li0.className = this.checkListItemCssClass;
ul.appendChild(li0);
addEvt(li0.check, 'click', evt => this.optionClick(evt));
Event.add(li0.check, 'click', (evt)=> this.optionClick(evt));
if (!this.enableResetOption) {
li0.style.display = NONE;
if(!this.enableCheckListResetFilter){
li0.style.display = 'none';
}
if (tf.enableEmptyOption) {
let li1 = createCheckItem(`${fltId}_1`, tf.emOperator,
tf.emptyText, ['data-idx', 1]);
li1.className = this.itemCssClass;
if(tf.enableEmptyOption){
let li1 = Dom.createCheckItem(
tf.fltIds[colIndex]+'_1', tf.emOperator, tf.emptyText);
li1.className = this.checkListItemCssClass;
ul.appendChild(li1);
addEvt(li1.check, 'click', evt => this.optionClick(evt));
Event.add(li1.check, 'click', (evt)=> this.optionClick(evt));
chkCt++;
}
if (tf.enableNonEmptyOption) {
let li2 = createCheckItem(`${fltId}_2`, tf.nmOperator,
tf.nonEmptyText, ['data-idx', 2]);
li2.className = this.itemCssClass;
if(tf.enableNonEmptyOption){
let li2 = Dom.createCheckItem(
tf.fltIds[colIndex]+'_2',
tf.nmOperator,
tf.nonEmptyText
);
li2.className = this.checkListItemCssClass;
ul.appendChild(li2);
addEvt(li2.check, 'click', evt => this.optionClick(evt));
Event.add(li2.check, 'click', (evt)=> this.optionClick(evt));
chkCt++;
}
return chkCt;
}
/**
* Set/unset value of passed item option in filter's DOM element attribute
* Store checked options in DOM element attribute
* @param {Object} o checklist option DOM element
* @private
*/
setItemOption(o) {
if (!o) {
setCheckListValues(o){
if(!o){
return;
}
let tf = this.tf;
let chkValue = o.value; //checked item value
let chkIndex = o.dataset.idx;
let colIdx = tf.getColumnIndexFromFilterId(o.id);
let n = tf.getFilterElement(parseInt(colIdx, 10));
let items = n.childNodes;
let li = items[chkIndex];
//selected values (ul tag)
let slcValues = n.getAttribute('value') || '';
//selected items indexes (ul tag)
let slcIndexes = n.getAttribute('indexes') || '';
let chkIndex = parseInt(o.id.split('_')[2], 10);
let filterTag = 'ul', itemTag = 'li';
let n = o;
if (o.checked) {
//ul tag search
while(Str.lower(n.nodeName)!==filterTag){
n = n.parentNode;
}
let li = n.childNodes[chkIndex];
let colIndex = n.getAttribute('colIndex');
let fltValue = n.getAttribute('value'); //filter value (ul tag)
let fltIndexes = n.getAttribute('indexes'); //selected items (ul tag)
if(o.checked){
//show all item
if (chkValue === '') {
//items indexes
let indexes = slcIndexes.split(tf.separator);
indexes.forEach(idx => {
idx = Number(idx);
let li = items[idx];
let chx = tag(li, 'input')[0];
if (chx && idx > 0) {
chx.checked = false;
removeClass(li, this.selectedItemCssClass);
if(chkValue===''){
if((fltIndexes && fltIndexes!=='')){
//items indexes
let indSplit = fltIndexes.split(tf.separator);
//checked items loop
for(let u=0; u<indSplit.length; u++){
//checked item
let cChk = Dom.id(tf.fltIds[colIndex]+'_'+indSplit[u]);
if(cChk){
cChk.checked = false;
Dom.removeClass(
n.childNodes[indSplit[u]],
this.checkListSlcItemCssClass
);
}
}
});
}
n.setAttribute('value', '');
n.setAttribute('indexes', '');
} else {
let indexes = slcIndexes + chkIndex + tf.separator;
let values =
trim(slcValues + ' ' + chkValue + ' ' + tf.orOperator);
n.setAttribute('value', values);
n.setAttribute('indexes', indexes);
//uncheck first option
let chx0 = tag(items[0], 'input')[0];
if (chx0) {
chx0.checked = false;
fltValue = (fltValue) ? fltValue : '';
chkValue = Str.trim(
fltValue+' '+chkValue+' '+tf.orOperator);
chkIndex = fltIndexes + chkIndex + tf.separator;
n.setAttribute('value', chkValue);
n.setAttribute('indexes', chkIndex);
//1st option unchecked
if(Dom.id(tf.fltIds[colIndex]+'_0')){
Dom.id(tf.fltIds[colIndex]+'_0').checked = false;
}
}
removeClass(items[0], this.selectedItemCssClass);
addClass(li, this.selectedItemCssClass);
if(Str.lower(li.nodeName) === itemTag){
Dom.removeClass(
n.childNodes[0], this.checkListSlcItemCssClass);
Dom.addClass(li, this.checkListSlcItemCssClass);
}
} else { //removes values and indexes
let replaceValue =
new RegExp(rgxEsc(chkValue + ' ' + tf.orOperator));
let values = slcValues.replace(replaceValue, '');
let replaceIndex = new RegExp(rgxEsc(chkIndex + tf.separator));
let indexes = slcIndexes.replace(replaceIndex, '');
if(chkValue!==''){
let replaceValue = new RegExp(
Str.rgxEsc(chkValue+' '+tf.orOperator));
fltValue = fltValue.replace(replaceValue,'');
n.setAttribute('value', Str.trim(fltValue));
n.setAttribute('value', trim(values));
n.setAttribute('indexes', indexes);
removeClass(li, this.selectedItemCssClass);
let replaceIndex = new RegExp(
Str.rgxEsc(chkIndex + tf.separator));
fltIndexes = fltIndexes.replace(replaceIndex, '');
n.setAttribute('indexes', fltIndexes);
}
if(Str.lower(li.nodeName)===itemTag){
Dom.removeClass(li, this.checkListSlcItemCssClass);
}
}
}
@ -442,78 +432,43 @@ export class CheckList extends BaseDropdown {
* @param {Number} colIndex Column index
* @param {Array} values Array of option values to select
*/
selectOptions(colIndex, values = []) {
selectOptions(colIndex, values=[]){
let tf = this.tf;
let flt = tf.getFilterElement(colIndex);
if (!flt || values.length === 0) {
if(tf.getFilterType(colIndex) !== tf.fltTypeCheckList ||
values.length === 0){
return;
}
let flt = tf.getFilterElement(colIndex);
let lis = tag(flt, 'li');
let lisNb = Dom.tag(flt, 'li').length;
flt.setAttribute('value', '');
flt.setAttribute('indexes', '');
[].forEach.call(lis, (li) => {
let chk = tag(li, 'input')[0];
let chkVal = matchCase(chk.value, tf.caseSensitive);
if (chkVal !== '' && has(values, chkVal, tf.caseSensitive)) {
for(let k=0; k<lisNb; k++){
let li = Dom.tag(flt, 'li')[k],
lbl = Dom.tag(li, 'label')[0],
chk = Dom.tag(li, 'input')[0],
lblTxt = Str.matchCase(Dom.getText(lbl), tf.caseSensitive);
if(lblTxt !== '' && Arr.has(values, lblTxt, tf.caseSensitive)){
chk.checked = true;
} else {
// Check non-empty-text or empty-text option
if (values.indexOf(tf.nmOperator) !== -1 &&
chkVal === matchCase(tf.nonEmptyText, tf.caseSensitive)) {
chk.checked = true;
}
else if (values.indexOf(tf.emOperator) !== -1 &&
chkVal === matchCase(tf.emptyText, tf.caseSensitive)) {
chk.checked = true;
} else {
chk.checked = false;
}
this.setCheckListValues(chk);
}
else{
chk.checked = false;
this.setCheckListValues(chk);
}
this.setItemOption(chk);
});
}
/**
* Get filter values for a given column index
* @param {Number} colIndex Column index
* @returns {Array} values Collection of selected values
*/
getValues(colIndex) {
let tf = this.tf;
let flt = tf.getFilterElement(colIndex);
if (!flt) {
return [];
}
let fltAttr = flt.getAttribute('value');
let values = isEmpty(fltAttr) ? '' : fltAttr;
//removes last operator ||
values = values.substr(0, values.length - 3);
//turn || separated values into array
values = values.split(' ' + tf.orOperator + ' ');
return values;
}
/**
* Destroy CheckList instance
*/
destroy() {
destroy(){
this.emitter.off(
['build-checklist-filter'],
(tf, colIndex, isLinked) => this.build(colIndex, isLinked)
(tf, colIndex, isExternal)=> this.build(colIndex, isExternal)
);
this.emitter.off(
['select-checklist-options'],
(tf, colIndex, values) => this.selectOptions(colIndex, values)
(tf, colIndex, values)=> this.selectOptions(colIndex, values)
);
this.emitter.off(['rows-changed'], () => this.refreshAll());
this.emitter.off(['after-filtering'], () => this.linkFilters());
this.initialized = false;
}
}

View file

@ -1,142 +1,94 @@
import {Feature} from '../feature';
import {createElm, createText, elm, removeElm} from '../dom';
import {addEvt} from '../event';
import {defaultsStr} from '../settings';
import {isNull} from '../types';
import {RIGHT} from './toolbar';
import {Feature} from './feature';
import Dom from '../dom';
import Event from '../event';
/**
* Clear button UI component
*/
export class ClearButton extends Feature {
export class ClearButton extends Feature{
/**
* Creates an instance of ClearButton
* @param {TableFilter} tf TableFilter instance
* Clear button component
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, ClearButton);
constructor(tf){
super(tf, 'btnReset');
let f = this.config.btn_reset || {};
// Configuration object
var f = this.config;
/**
* Container element ID
* @type {String}
*/
this.targetId = defaultsStr(f.target_id, null);
/**
* Text for the clear button
* @type {String}
*/
this.text = defaultsStr(f.text, null);
/**
* Css class for reset button
* @type {String}
*/
this.cssClass = defaultsStr(f.css_class, 'reset');
/**
* Tooltip text for the clear button
* @type {String}
*/
this.tooltip = f.tooltip || 'Clear filters';
/**
* Custom Html string for the clear button
* @type {String}
*/
this.html = defaultsStr(f.html,
(!tf.enableIcons || this.text ? null :
'<input type="button" value="" class="' + this.cssClass +
'" ' + 'title="' + this.tooltip + '" />'));
/**
* Default position in toolbar ('left'|'center'|'right')
* @type {String}
*/
this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);
/**
* Clear button container element
* @type {DOMElement}
* @private
*/
this.container = null;
/**
* Clear button element
* @type {DOMElement}
* @private
*/
this.element = null;
//id of container element
this.btnResetTgtId = f.btn_reset_target_id || null;
//reset button element
this.btnResetEl = null;
//defines reset text
this.btnResetText = f.btn_reset_text || 'Reset';
//defines reset button tooltip
this.btnResetTooltip = f.btn_reset_tooltip || 'Clear filters';
//defines reset button innerHtml
this.btnResetHtml = f.btn_reset_html ||
(!tf.enableIcons ? null :
'<input type="button" value="" class="'+tf.btnResetCssClass+'" ' +
'title="'+this.btnResetTooltip+'" />');
//span containing reset button
this.prfxResetSpan = 'resetspan_';
}
/**
* Click event handler for clear button
* @private
*/
onClick() {
if (!this.isEnabled()) {
onClick(){
if(!this.isEnabled()){
return;
}
this.tf.clearFilters();
}
/**
* Initialize clear button component
* Build DOM elements
*/
init() {
let tf = this.tf;
init(){
var tf = this.tf;
if (this.initialized) {
if(this.initialized){
return;
}
this.emitter.emit('initializing-feature', this, !isNull(this.targetId));
var resetspan = Dom.create('span', ['id', this.prfxResetSpan+tf.id]);
let cont = createElm('span');
let targetEl = !this.targetId ?
tf.feature('toolbar').container(this.toolbarPosition) :
elm(this.targetId);
targetEl.appendChild(cont);
if (!this.html) {
let fltReset = createElm('a', ['href', 'javascript:void(0);']);
fltReset.className = this.cssClass;
fltReset.appendChild(createText(this.text));
cont.appendChild(fltReset);
addEvt(fltReset, 'click', () => this.onClick());
} else {
cont.innerHTML = this.html;
let resetEl = cont.firstChild;
addEvt(resetEl, 'click', () => this.onClick());
// reset button is added to defined element
if(!this.btnResetTgtId){
tf.setToolbar();
}
this.element = cont.firstChild;
this.container = cont;
var targetEl = !this.btnResetTgtId ?
tf.rDiv : Dom.id(this.btnResetTgtId);
targetEl.appendChild(resetspan);
if(!this.btnResetHtml){
var fltreset = Dom.create('a', ['href', 'javascript:void(0);']);
fltreset.className = tf.btnResetCssClass;
fltreset.appendChild(Dom.text(this.btnResetText));
resetspan.appendChild(fltreset);
Event.add(fltreset, 'click', ()=> { this.onClick(); });
} else {
resetspan.innerHTML = this.btnResetHtml;
var resetEl = resetspan.firstChild;
Event.add(resetEl, 'click', ()=> { this.onClick(); });
}
this.btnResetEl = resetspan.firstChild;
/** @inherited */
this.initialized = true;
this.emitter.emit('feature-initialized', this);
}
/**
* Destroy ClearButton instance
* Remove clear button UI
*/
destroy() {
if (!this.initialized) {
destroy(){
var tf = this.tf;
if(!this.initialized){
return;
}
removeElm(this.element);
removeElm(this.container);
this.element = null;
this.container = null;
var resetspan = Dom.id(this.prfxResetSpan+tf.id);
if(resetspan){
Dom.remove(resetspan);
}
this.btnResetEl = null;
this.initialized = false;
}
}
// TODO: remove as soon as feature name is fixed
ClearButton.meta = {altName: 'btnReset'};

View file

@ -1,157 +0,0 @@
import {Date as SugarDate} from 'sugar-date';
import 'sugar-date/locales';
import {Feature} from '../feature';
import {isObj, isArray} from '../types';
import {DATE} from '../const';
import {root} from '../root';
/**
* Wrapper for Sugar Date module providing datetime helpers and locales
* @export
* @class DateType
*/
export class DateType extends Feature {
/**
* Creates an instance of DateType
* @param {TableFilter} tf TableFilter instance
*/
constructor(tf) {
super(tf, DateType);
/**
* Global locale
* @type {String}
*/
this.locale = tf.locale;
/**
* Sugar Date instance
* @type {Object}
*/
this.datetime = SugarDate;
this.enable();
}
/**
* Initialize DateType instance
*/
init() {
if (this.initialized) {
return;
}
// Set global locale
this.datetime.setLocale(this.locale);
// Add formats from column types configuration if any
this.addConfigFormats(this.tf.colTypes);
this.emitter.on(
['add-date-type-formats'],
(tf, types) => this.addConfigFormats(types)
);
// Broadcast date-type initialization
this.emitter.emit('date-type-initialized', this.tf, this);
/** @inherited */
this.initialized = true;
}
/**
* Parse a string representation of a date for a specified locale and return
* a date object
* @param {String} dateStr String representation of a date
* @param {String} localeCode Locale code (ie 'en-us')
* @returns {Date}
*/
parse(dateStr, localeCode) {
return this.datetime.create(dateStr, localeCode);
}
/**
* Check string representation of a date for a specified locale is valid
* @param {any} dateStr String representation of a date
* @param {any} localeCode Locale code (ie 'en-us')
* @returns {Boolean}
*/
isValid(dateStr, localeCode) {
return this.datetime.isValid(this.parse(dateStr, localeCode));
}
/**
* Return the type object of a specified column as per configuration or
* passed collection
* @param {Number} colIndex Column index
* @param {Array} types Collection of column types, optional
* @returns {Object}
*/
getOptions(colIndex, types) {
types = types || this.tf.colTypes;
let colType = types[colIndex];
return isObj(colType) ? colType : {};
}
/**
* Return the locale code for supplied column index as per configuration
* or global setting
* @param {Number} colIndex Column index
* @returns {String} Locale code (ie: 'en-us')
*/
getLocale(colIndex) {
return this.getOptions(colIndex).locale || this.locale;
}
/**
* Add date time format(s) to a locale as specified by the passed
* collection of column types, ie:
* [
* 'string',
* 'number',
* { type: 'date', locale: 'en', format: ['{dd}/{MM}/{yyyy}']}
* ]
*
* @param {Array} [types=[]] Collection of column types
*/
addConfigFormats(types=[]) {
types.forEach((type, idx) => {
let options = this.getOptions(idx, types);
if (options.type === DATE && options.hasOwnProperty('format')) {
let locale = this.datetime.getLocale(
options.locale || this.locale
);
let formats = isArray(options.format) ?
options.format : [options.format];
// Sugar date module throws exceptions with locale.addFormat
try {
formats.forEach((format) => {
locale.addFormat(format);
});
} catch (ex) {
root.console.error(ex);
}
}
});
}
/**
* Remove DateType instance
*/
destroy() {
if (!this.initialized) {
return;
}
// TODO: remove added formats
this.emitter.off(
['add-date-type-formats'],
(tf, types) => this.addConfigFormats(types)
);
this.initialized = false;
}
}

View file

@ -1,236 +1,263 @@
import {BaseDropdown} from './baseDropdown';
import {createElm, createOpt, elm} from '../dom';
import {has} from '../array';
import {matchCase} from '../string';
import {addEvt, targetEvt} from '../event';
import {SELECT, MULTIPLE, NONE} from '../const';
import {defaultsStr, defaultsBool} from '../settings';
import {Feature} from './feature';
import Dom from '../dom';
import Arr from '../array';
import Str from '../string';
import Sort from '../sort';
import Event from '../event';
/**
* Dropdown filter UI component
* @export
* @class Dropdown
* @extends {BaseDropdown}
*/
export class Dropdown extends BaseDropdown {
const SORT_ERROR = 'Filter options for column {0} cannot be sorted in ' +
'{1} manner.';
export class Dropdown extends Feature{
/**
* Creates an instance of Dropdown
* @param {TableFilter} tf TableFilter instance
* Dropdown UI component
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, Dropdown);
constructor(tf){
super(tf, 'dropdown');
// Configuration object
let f = this.config;
let f = tf.config();
/**
* Enable the reset filter option as first item
* @type {Boolean}
*/
this.enableSlcResetFilter =
defaultsBool(f.enable_slc_reset_filter, true);
this.enableSlcResetFilter = f.enable_slc_reset_filter===false ?
false : true;
//defines empty option text
this.nonEmptyText = f.non_empty_text || '(Non empty)';
//IE only, tooltip text appearing on select before it is populated
this.activateSlcTooltip = f.activate_slc_tooltip ||
'Click to activate';
//tooltip text appearing on multiple select
this.multipleSlcTooltip = f.multiple_slc_tooltip ||
'Use Ctrl key for multiple selections';
/**
* Non empty option text
* @type {String}
*/
this.nonEmptyText = defaultsStr(f.non_empty_text, '(Non empty)');
/**
* Tooltip text appearing on multiple select
* @type {String}
*/
this.multipleSlcTooltip = defaultsStr(f.multiple_slc_tooltip,
'Use Ctrl/Cmd key for multiple selections');
this.isCustom = null;
this.opts = null;
this.optsTxt = null;
this.slcInnerHtml = null;
}
/**
* Drop-down filter focus event handler
* @param {Event} e DOM Event
* @private
*/
onSlcFocus(e) {
let elm = targetEvt(e);
let elm = Event.target(e);
let tf = this.tf;
tf.activeFilterId = elm.getAttribute('id');
tf.activeFlt = Dom.id(tf.activeFilterId);
// select is populated when element has focus
if (tf.loadFltOnDemand && elm.getAttribute('filled') === '0') {
if(tf.loadFltOnDemand && elm.getAttribute('filled') === '0'){
let ct = elm.getAttribute('ct');
this.build(ct);
}
this.emitter.emit('filter-focus', tf, elm);
this.emitter.emit('filter-focus', tf, this);
}
/**
* Drop-down filter change event handler
* @private
*/
onSlcChange() {
if (this.tf.onSlcChange) {
if(this.tf.onSlcChange){
this.tf.filter();
}
}
/**
* Refresh all drop-down filters
*/
refreshAll() {
let selectFlts = this.tf.getFiltersByType(SELECT, true);
let multipleFlts = this.tf.getFiltersByType(MULTIPLE, true);
let colIdxs = selectFlts.concat(multipleFlts);
this.refreshFilters(colIdxs);
}
/**
* Initialize drop-down filter
* @param {Number} colIndex Column index
* @param {Boolean} isExternal External filter flag
* @param {DOMElement} container Dom element containing the filter
*/
init(colIndex, isExternal, container) {
init(colIndex, isExternal, container){
let tf = this.tf;
let col = tf.getFilterType(colIndex);
let externalFltTgtId = isExternal ?
tf.externalFltIds[colIndex] : null;
tf.externalFltTgtIds[colIndex] : null;
let slc = createElm(SELECT,
['id', tf.buildFilterId(colIndex)],
let slc = Dom.create(tf.fltTypeSlc,
['id', tf.prfxFlt+colIndex+'_'+tf.id],
['ct', colIndex], ['filled', '0']
);
if (col === MULTIPLE) {
slc.multiple = MULTIPLE;
if(col === tf.fltTypeMulti){
slc.multiple = tf.fltTypeMulti;
slc.title = this.multipleSlcTooltip;
}
slc.className = col.toLowerCase() === SELECT ?
slc.className = Str.lower(col) === tf.fltTypeSlc ?
tf.fltCssClass : tf.fltMultiCssClass;
//filter is appended in container element
if (externalFltTgtId) {
elm(externalFltTgtId).appendChild(slc);
if(externalFltTgtId){
Dom.id(externalFltTgtId).appendChild(slc);
tf.externalFltEls.push(slc);
} else {
container.appendChild(slc);
}
tf.fltIds.push(slc.id);
if (!tf.loadFltOnDemand) {
if(!tf.loadFltOnDemand){
this.build(colIndex);
} else {
//1st option is created here since build isn't invoked
let opt0 = createOpt(tf.getClearFilterText(colIndex), '');
let opt0 = Dom.createOpt(tf.displayAllText, '');
slc.appendChild(opt0);
}
addEvt(slc, 'change', () => this.onSlcChange());
addEvt(slc, 'focus', (e) => this.onSlcFocus(e));
Event.add(slc, 'change', ()=> this.onSlcChange());
Event.add(slc, 'focus', (e)=> this.onSlcFocus(e));
this.emitter.on(
['build-select-filter'],
(tf, colIndex, isLinked, isExternal) =>
(tf, colIndex, isLinked, isExternal)=>
this.build(colIndex, isLinked, isExternal)
);
this.emitter.on(
['select-options'],
(tf, colIndex, values) => this.selectOptions(colIndex, values)
(tf, colIndex, values)=> this.selectOptions(colIndex, values)
);
this.emitter.on(['rows-changed'], () => this.refreshAll());
this.emitter.on(['after-filtering'], () => this.linkFilters());
/** @inherited */
this.initialized = true;
}
/**
* Build drop-down filter UI
* @param {Number} colIndex Column index
* @param {Boolean} isLinked Enable linked filters behaviour
* @param {Boolean} isLinked Enable linked refresh behaviour
* @param {Boolean} isExternal Render in external container
* @param {String} extSlcId External container id
*/
build(colIndex, isLinked = false) {
build(colIndex, isLinked=false, isExternal=false, extSlcId=null){
let tf = this.tf;
colIndex = Number(colIndex);
colIndex = parseInt(colIndex, 10);
this.emitter.emit('before-populating-filter', tf, colIndex);
/** @inherited */
this.opts = [];
/** @inherited */
this.optsTxt = [];
this.slcInnerHtml = '';
let slc = tf.getFilterElement(colIndex);
let slcId = tf.fltIds[colIndex];
if((!Dom.id(slcId) && !isExternal) ||
(!Dom.id(extSlcId) && isExternal)){
return;
}
let slc = !isExternal ? Dom.id(slcId) : Dom.id(extSlcId),
rows = tf.tbl.rows,
matchCase = tf.matchCase;
//custom select test
/** @inherited */
this.isCustom = tf.isCustomOptions(colIndex);
//custom selects text
let activeFlt;
if(isLinked && tf.activeFilterId){
activeFlt = tf.activeFilterId.split('_')[0];
activeFlt = activeFlt.split(tf.prfxFlt)[1];
}
let excludedOpts = null,
filteredDataCol = null;
if(isLinked && tf.disableExcludedOptions){
excludedOpts = [];
filteredDataCol = [];
}
for(let k=tf.refRow; k<tf.nbRows; k++){
// always visible rows don't need to appear on selects as always
// valid
if(tf.hasVisibleRows && tf.visibleRows.indexOf(k) !== -1){
continue;
}
let cell = rows[k].cells,
nchilds = cell.length;
// checks if row has exact cell #
if(nchilds !== tf.nbCells || this.isCustom){
continue;
}
// this loop retrieves cell data
for(let j=0; j<nchilds; j++){
// WTF: cyclomatic complexity hell
if((colIndex===j &&
(!isLinked ||
(isLinked && tf.disableExcludedOptions))) ||
(colIndex==j && isLinked &&
((rows[k].style.display === '' && !tf.paging) ||
(tf.paging && (!tf.validRowsIndex ||
(tf.validRowsIndex &&
tf.validRowsIndex.indexOf(k) != -1)) &&
((activeFlt===undefined || activeFlt==colIndex) ||
(activeFlt!=colIndex &&
tf.validRowsIndex.indexOf(k) != -1 ))) ))){
let cell_data = tf.getCellData(cell[j]),
//Vary Peter's patch
cell_string = Str.matchCase(cell_data, matchCase);
// checks if celldata is already in array
if(!Arr.has(this.opts, cell_string, matchCase)){
this.opts.push(cell_data);
}
if(isLinked && tf.disableExcludedOptions){
let filteredCol = filteredDataCol[j];
if(!filteredCol){
filteredCol = tf.getFilteredDataCol(j);
}
if(!Arr.has(filteredCol, cell_string, matchCase) &&
!Arr.has(
excludedOpts, cell_string, matchCase)){
excludedOpts.push(cell_data);
}
}
}//if colIndex==j
}//for j
}//for k
//Retrieves custom values
if (this.isCustom) {
if(this.isCustom){
let customValues = tf.getCustomOptions(colIndex);
this.opts = customValues[0];
this.optsTxt = customValues[1];
}
//custom selects text
let activeIdx;
let activeFilterId = tf.getActiveFilterId();
if (isLinked && activeFilterId) {
activeIdx = tf.getColumnIndexFromFilterId(activeFilterId);
}
let excludedOpts = null,
filteredDataCol = null;
if (isLinked && tf.disableExcludedOptions) {
excludedOpts = [];
filteredDataCol = [];
}
let eachRow = tf.eachRow();
eachRow(
(row) => {
let cellValue = tf.getCellValue(row.cells[colIndex]);
//Vary Peter's patch
let cellString = matchCase(cellValue, tf.caseSensitive);
// checks if celldata is already in array
if (!has(this.opts, cellString, tf.caseSensitive)) {
this.opts.push(cellValue);
}
if (isLinked && tf.disableExcludedOptions) {
let filteredCol = filteredDataCol[colIndex];
if (!filteredCol) {
filteredCol = tf.getVisibleColumnValues(colIndex);
}
if (!has(filteredCol, cellString, tf.caseSensitive) &&
!has(excludedOpts, cellString, tf.caseSensitive)) {
excludedOpts.push(cellValue);
}
}
},
// continue conditions function
(row, k) => {
// excluded rows don't need to appear on selects as always valid
if (tf.excludeRows.indexOf(k) !== -1) {
return true;
}
// checks if row has expected number of cells
if (row.cells.length !== tf.nbCells || this.isCustom) {
return true;
}
if (isLinked && !this.isValidLinkedValue(k, activeIdx)) {
return true;
if(tf.sortSlc && !this.isCustom){
if (!matchCase){
this.opts.sort(Sort.ignoreCase);
if(excludedOpts){
excludedOpts.sort(Sort.ignoreCase);
}
} else {
this.opts.sort();
if(excludedOpts){ excludedOpts.sort(); }
}
);
}
//sort options
this.opts = this.sortOptions(colIndex, this.opts);
if (excludedOpts) {
excludedOpts = this.sortOptions(colIndex, excludedOpts);
//asc sort
if(tf.sortNumAsc.indexOf(colIndex) != -1){
try{
this.opts.sort(Sort.numSortAsc);
if(excludedOpts){
excludedOpts.sort(Sort.numSortAsc);
}
if(this.isCustom){
this.optsTxt.sort(Sort.numSortAsc);
}
} catch(e) {
throw new Error(SORT_ERROR.replace('{0}', colIndex)
.replace('{1}', 'ascending'));
}//in case there are alphanumeric values
}
//desc sort
if(tf.sortNumDesc.indexOf(colIndex) != -1){
try{
this.opts.sort(Sort.numSortDesc);
if(excludedOpts){
excludedOpts.sort(Sort.numSortDesc);
}
if(this.isCustom){
this.optsTxt.sort(Sort.numSortDesc);
}
} catch(e) {
throw new Error(SORT_ERROR.replace('{0}', colIndex)
.replace('{1}', 'ascending'));
}//in case there are alphanumeric values
}
//populates drop-down
@ -246,35 +273,38 @@ export class Dropdown extends BaseDropdown {
* @param {Boolean} isLinked Enable linked refresh behaviour
* @param {Array} excludedOpts Array of excluded options
*/
addOptions(colIndex, slc, isLinked, excludedOpts) {
addOptions(colIndex, slc, isLinked, excludedOpts){
let tf = this.tf,
slcValue = slc.value;
slc.innerHTML = '';
slc = this.addFirstOption(slc);
for (let y = 0; y < this.opts.length; y++) {
if (this.opts[y] === '') {
for(let y=0; y<this.opts.length; y++){
if(this.opts[y]===''){
continue;
}
let val = this.opts[y]; //option value
let lbl = this.isCustom ? this.optsTxt[y] : val; //option text
let isDisabled = false;
if (isLinked && tf.disableExcludedOptions &&
has(excludedOpts, matchCase(val, tf.caseSensitive),
tf.caseSensitive)) {
if(isLinked && tf.disableExcludedOptions &&
Arr.has(
excludedOpts,
Str.matchCase(val, tf.matchCase),
tf.matchCase
)){
isDisabled = true;
}
let opt;
//fill select on demand
if (tf.loadFltOnDemand && slcValue === this.opts[y] &&
tf.getFilterType(colIndex) === SELECT) {
opt = createOpt(lbl, val, true);
if(tf.loadFltOnDemand && slcValue===this.opts[y] &&
tf.getFilterType(colIndex) === tf.fltTypeSlc){
opt = Dom.createOpt(lbl, val, true);
} else {
opt = createOpt(lbl, val, false);
opt = Dom.createOpt(lbl, val, false);
}
if (isDisabled) {
if(isDisabled){
opt.disabled = true;
}
slc.appendChild(opt);
@ -287,21 +317,21 @@ export class Dropdown extends BaseDropdown {
* Add drop-down header option
* @param {Object} slc Select DOM element
*/
addFirstOption(slc) {
addFirstOption(slc){
let tf = this.tf;
let colIdx = tf.getColumnIndexFromFilterId(slc.id);
let opt0 = createOpt((!this.enableSlcResetFilter ?
'' : tf.getClearFilterText(colIdx)), '');
if (!this.enableSlcResetFilter) {
opt0.style.display = NONE;
let opt0 = Dom.createOpt(
(!this.enableSlcResetFilter ? '' : tf.displayAllText),'');
if(!this.enableSlcResetFilter){
opt0.style.display = 'none';
}
slc.appendChild(opt0);
if (tf.enableEmptyOption) {
let opt1 = createOpt(tf.emptyText, tf.emOperator);
if(tf.enableEmptyOption){
let opt1 = Dom.createOpt(tf.emptyText, tf.emOperator);
slc.appendChild(opt1);
}
if (tf.enableNonEmptyOption) {
let opt2 = createOpt(tf.nonEmptyText, tf.nmOperator);
if(tf.enableNonEmptyOption){
let opt2 = Dom.createOpt(tf.nonEmptyText, tf.nmOperator);
slc.appendChild(opt2);
}
return slc;
@ -312,65 +342,36 @@ export class Dropdown extends BaseDropdown {
* @param {Number} colIndex Column index
* @param {Array} values Array of option values to select
*/
selectOptions(colIndex, values = []) {
selectOptions(colIndex, values=[]){
let tf = this.tf;
if (values.length === 0) {
if(tf.getFilterType(colIndex) !== tf.fltTypeMulti ||
values.length === 0){
return;
}
let slc = tf.getFilterElement(colIndex);
[].forEach.call(slc.options, (option) => {
[].forEach.call(slc.options, (option)=> {
// Empty value means clear all selections and first option is the
// clear all option
if (values[0] === '' || option.value === '') {
if(values[0] === '' || option.value === ''){
option.selected = false;
}
if (option.value !== '' && has(values, option.value, true)) {
if(option.value !== '' &&
Arr.has(values, option.value, true)){
option.selected = true;
}//if
});
}
/**
* Get filter values for a given column index
* @param {Number} colIndex Column index
* @returns {Array} values Array of selected values
*/
getValues(colIndex) {
let tf = this.tf;
let slc = tf.getFilterElement(colIndex);
let values = [];
// IE >= 9 does not support the selectedOptions property :(
if (slc.selectedOptions) {
[].forEach.call(slc.selectedOptions,
option => values.push(option.value));
} else {
[].forEach.call(slc.options, (option) => {
if (option.selected) {
values.push(option.value);
}
});
}
return values;
}
/**
* Destroy Dropdown instance
*/
destroy() {
destroy(){
this.emitter.off(
['build-select-filter'],
(colIndex, isLinked, isExternal) =>
(colIndex, isLinked, isExternal)=>
this.build(colIndex, isLinked, isExternal)
);
this.emitter.off(
['select-options'],
(tf, colIndex, values) => this.selectOptions(colIndex, values)
(tf, colIndex, values)=> this.selectOptions(colIndex, values)
);
this.emitter.off(['rows-changed'], () => this.refreshAll());
this.emitter.off(['after-filtering'], () => this.linkFilters());
this.initialized = false;
}
}

38
src/modules/feature.js Normal file
View file

@ -0,0 +1,38 @@
const NOTIMPLEMENTED = 'Not implemented.';
export class Feature {
constructor(tf, feature) {
this.tf = tf;
this.feature = feature;
this.enabled = tf[feature];
this.config = tf.config();
this.emitter = tf.emitter;
this.initialized = false;
}
init() {
throw new Error(NOTIMPLEMENTED);
}
reset() {
this.enable();
this.init();
}
destroy() {
throw new Error(NOTIMPLEMENTED);
}
enable() {
this.enabled = true;
}
disable() {
this.enabled = false;
}
isEnabled() {
return this.enabled;
}
}

View file

@ -1,223 +1,195 @@
import {Feature} from '../feature';
import {createElm, removeElm, elm, tag} from '../dom';
import {addEvt, targetEvt} from '../event';
import {contains} from '../string';
import {NONE} from '../const';
import {
defaultsBool, defaultsStr, defaultsNb, defaultsArr
} from '../settings';
import {Feature} from './feature';
import Dom from '../dom';
import Types from '../types';
import Event from '../event';
import Str from '../string';
/**
* Grid layout, table with fixed headers
*/
export class GridLayout extends Feature {
export class GridLayout extends Feature{
/**
* Creates an instance of GridLayout
* @param {TableFilter} tf TableFilter instance
* Grid layout, table with fixed headers
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, GridLayout);
constructor(tf){
super(tf, 'gridLayout');
let f = this.config.grid_layout || {};
let f = this.config;
/**
* Grid-layout container width as CSS string
* @type {String}
*/
this.width = defaultsStr(f.width, null);
//defines grid width
this.gridWidth = f.grid_width || null;
//defines grid height
this.gridHeight = f.grid_height || null;
//defines css class for main container
this.gridMainContCssClass = f.grid_cont_css_class || 'grd_Cont';
//defines css class for div containing table
this.gridContCssClass = f.grid_tbl_cont_css_class || 'grd_tblCont';
//defines css class for div containing headers' table
this.gridHeadContCssClass = f.grid_tblHead_cont_css_class ||
'grd_headTblCont';
//defines css class for div containing rows counter, paging etc.
this.gridInfDivCssClass = f.grid_inf_grid_css_class || 'grd_inf';
//defines which row contains column headers
this.gridHeadRowIndex = f.grid_headers_row_index || 0;
//array of headers row indexes to be placed in header table
this.gridHeadRows = f.grid_headers_rows || [0];
//generate filters in table headers
this.gridEnableFilters = f.grid_enable_default_filters!==undefined ?
f.grid_enable_default_filters : true;
//default col width
this.gridDefaultColWidth = f.grid_default_col_width || '100px';
/**
* Grid-layout container height as CSS string
* @type {String}
*/
this.height = defaultsStr(f.height, null);
this.gridColElms = [];
/**
* Css class for main container element
* @type {String}
*/
this.mainContCssClass = defaultsStr(f.cont_css_class, 'grd_Cont');
/**
* Css class for body table container element
* @type {String}
*/
this.contCssClass = defaultsStr(f.tbl_cont_css_class, 'grd_tblCont');
/**
* Css class for headers table container element
* @type {String}
*/
this.headContCssClass = defaultsStr(f.tbl_head_css_class,
'grd_headTblCont');
/**
* Css class for toolbar container element (rows counter, paging etc.)
* @type {String}
*/
this.infDivCssClass = defaultsStr(f.inf_grid_css_class, 'grd_inf');
/**
* Index of the headers row, default: 0
* @type {Number}
*/
this.headRowIndex = defaultsNb(f.headers_row_index, 0);
/**
* Collection of the header row indexes to be moved into headers table
* @type {Array}
*/
this.headRows = defaultsArr(f.headers_rows, [0]);
/**
* Enable or disable column filters generation, default: true
* @type {Boolean}
*/
this.filters = defaultsBool(f.filters, true);
/**
* Enable or disable column headers, default: false
* @type {Boolean}
*/
this.noHeaders = Boolean(f.no_headers);
/**
* Grid-layout default column widht as CSS string
* @type {String}
*/
this.defaultColWidth = defaultsStr(f.default_col_width, '100px');
/**
* List of column elements
* @type {Array}
* @private
*/
this.colElms = [];
/**
* Prefix for grid-layout filter's cell ID
* @type {String}
* @private
*/
//div containing grid elements if grid_layout true
this.prfxMainTblCont = 'gridCont_';
//div containing table if grid_layout true
this.prfxTblCont = 'tblCont_';
//div containing headers table if grid_layout true
this.prfxHeadTblCont = 'tblHeadCont_';
//headers' table if grid_layout true
this.prfxHeadTbl = 'tblHead_';
//id of td containing the filter if grid_layout true
this.prfxGridFltTd = '_td_';
/**
* Prefix for grid-layout header's cell ID
* @type {String}
* @private
*/
//id of th containing column header if grid_layout true
this.prfxGridTh = 'tblHeadTh_';
/**
* Mark-up of original HTML table
* @type {String}
* @private
*/
this.sourceTblHtml = tf.dom().outerHTML;
/**
* Indicates if working table has column elements
* @type {Boolean}
* @private
*/
this.tblHasColTag = tag(tf.dom(), 'col').length > 0 ? true : false;
/**
* Main container element
* @private
*/
this.tblMainCont = null;
/**
* Table container element
* @private
*/
this.tblCont = null;
/**
* Headers' table container element
* @private
*/
this.headTblCont = null;
/**
* Headers' table element
* @private
*/
this.headTbl = null;
this.sourceTblHtml = tf.tbl.outerHTML;
// filters flag at TF level
tf.fltGrid = this.filters;
tf.fltGrid = this.gridEnableFilters;
}
/**
* Generates a grid with fixed headers
* TODO: reduce size of init by extracting single purposed methods
*/
init() {
init(){
let tf = this.tf;
let tbl = tf.dom();
let f = this.config;
let tbl = tf.tbl;
if (this.initialized) {
if(this.initialized){
return;
}
// Override relevant TableFilter properties
this.setOverrides();
// Override reference rows indexes
tf.refRow = Types.isNull(tf.startRow) ? 0 : tf.startRow;
tf.headersRow = 0;
tf.filtersRowIndex = 1;
// Assign default column widths
this.setDefaultColWidths();
tf.isExternalFlt = true;
// default width of 100px if column widths not set
if(!tf.hasColWidths){
tf.colWidths = [];
for(let k=0; k<tf.nbCells; k++){
let colW,
cell = tbl.rows[this.gridHeadRowIndex].cells[k];
if(cell.width !== ''){
colW = cell.width;
} else if(cell.style.width !== ''){
colW = parseInt(cell.style.width, 10);
} else {
colW = this.gridDefaultColWidth;
}
tf.colWidths[k] = colW;
}
tf.hasColWidths = true;
}
tf.setColWidths(this.gridHeadRowIndex);
let tblW;//initial table width
if(tbl.width !== ''){
tblW = tbl.width;
}
else if(tbl.style.width !== ''){
tblW = parseInt(tbl.style.width, 10);
} else {
tblW = tbl.clientWidth;
}
//Main container: it will contain all the elements
this.tblMainCont = this.createContainer(
'div', this.mainContCssClass);
if (this.width) {
this.tblMainCont.style.width = this.width;
this.tblMainCont = Dom.create('div',
['id', this.prfxMainTblCont + tf.id]);
this.tblMainCont.className = this.gridMainContCssClass;
if(this.gridWidth){
this.tblMainCont.style.width = this.gridWidth;
}
tbl.parentNode.insertBefore(this.tblMainCont, tbl);
//Table container: div wrapping content table
this.tblCont = this.createContainer('div', this.contCssClass);
this.setConfigWidth(this.tblCont);
if (this.height) {
this.tblCont.style.height = this.height;
this.tblCont = Dom.create('div',['id', this.prfxTblCont + tf.id]);
this.tblCont.className = this.gridContCssClass;
if(this.gridWidth){
if(this.gridWidth.indexOf('%') != -1){
this.tblCont.style.width = '100%';
} else {
this.tblCont.style.width = this.gridWidth;
}
}
if(this.gridHeight){
this.tblCont.style.height = this.gridHeight;
}
tbl.parentNode.insertBefore(this.tblCont, tbl);
let t = removeElm(tbl);
let t = Dom.remove(tbl);
this.tblCont.appendChild(t);
//In case table width is expressed in %
if (tbl.style.width === '') {
let tblW = this.initialTableWidth();
tbl.style.width = (contains('%', tblW) ?
if(tbl.style.width === ''){
tbl.style.width = (Str.contains('%', tblW) ?
tbl.clientWidth : tblW) + 'px';
}
let d = removeElm(this.tblCont);
let d = Dom.remove(this.tblCont);
this.tblMainCont.appendChild(d);
//Headers table container: div wrapping headers table
this.headTblCont = this.createContainer(
'div', this.headContCssClass);
this.headTblCont = Dom.create(
'div',['id', this.prfxHeadTblCont + tf.id]);
this.headTblCont.className = this.gridHeadContCssClass;
if(this.gridWidth){
if(this.gridWidth.indexOf('%') != -1){
this.headTblCont.style.width = '100%';
} else {
this.headTblCont.style.width = this.gridWidth;
}
}
//Headers table
this.headTbl = createElm('table');
let tH = createElm('tHead');
this.headTbl = Dom.create('table', ['id', this.prfxHeadTbl + tf.id]);
let tH = Dom.create('tHead');
//1st row should be headers row, ids are added if not set
//Those ids are used by the sort feature
let hRow = tbl.rows[this.headRowIndex];
let sortTriggers = this.getSortTriggerIds(hRow);
let hRow = tbl.rows[this.gridHeadRowIndex];
let sortTriggers = [];
for(let n=0; n<tf.nbCells; n++){
let c = hRow.cells[n];
let thId = c.getAttribute('id');
if(!thId || thId===''){
thId = this.prfxGridTh+n+'_'+tf.id;
c.setAttribute('id', thId);
}
sortTriggers.push(thId);
}
//Filters row is created
let filtersRow = this.createFiltersRow();
let filtersRow = Dom.create('tr');
if(this.gridEnableFilters && tf.fltGrid){
tf.externalFltTgtIds = [];
for(let j=0; j<tf.nbCells; j++){
let fltTdId = tf.prfxFlt+j+ this.prfxGridFltTd +tf.id;
let cl = Dom.create(tf.fltCellTag, ['id', fltTdId]);
filtersRow.appendChild(cl);
tf.externalFltTgtIds[j] = fltTdId;
}
}
//Headers row are moved from content table to headers table
this.setHeadersRow(tH);
for(let i=0; i<this.gridHeadRows.length; i++){
let headRow = tbl.rows[this.gridHeadRows[0]];
tH.appendChild(headRow);
}
this.headTbl.appendChild(tH);
if (tf.filtersRowIndex === 0) {
if(tf.filtersRowIndex === 0){
tH.insertBefore(filtersRow, hRow);
} else {
tH.appendChild(filtersRow);
@ -227,26 +199,32 @@ export class GridLayout extends Feature {
this.tblCont.parentNode.insertBefore(this.headTblCont, this.tblCont);
//THead needs to be removed in content table for sort feature
let thead = tag(tbl, 'thead');
if (thead.length > 0) {
let thead = Dom.tag(tbl, 'thead');
if(thead.length>0){
tbl.removeChild(thead[0]);
}
// ensure table layout is always set even if already set in css
// definitions, potentially with custom css class this could be lost
//Headers table style
this.headTbl.style.tableLayout = 'fixed';
tbl.style.tableLayout = 'fixed';
this.headTbl.cellPadding = tbl.cellPadding;
this.headTbl.cellSpacing = tbl.cellSpacing;
// this.headTbl.style.width = tbl.style.width;
//content table without headers needs col widths to be reset
tf.setColWidths(this.headTbl);
tf.setColWidths(0, this.headTbl);
//Headers container width
this.headTbl.style.width = tbl.style.width;
// this.headTblCont.style.width = this.tblCont.clientWidth+'px';
tbl.style.width = '';
//
this.headTbl.style.width = tbl.clientWidth + 'px';
//
//scroll synchronisation
addEvt(this.tblCont, 'scroll', (evt) => {
let elm = targetEvt(evt);
Event.add(this.tblCont, 'scroll', (evt)=> {
let elm = Event.target(evt);
let scrollLeft = elm.scrollLeft;
this.headTblCont.scrollLeft = scrollLeft;
//New pointerX calc taking into account scrollLeft
@ -266,208 +244,87 @@ export class GridLayout extends Feature {
// }
});
// TODO: Trigger a custom event handled by sort extension
let sort = tf.extension('sort');
if (sort) {
sort.asyncSort = true;
sort.triggerIds = sortTriggers;
//Configure sort extension if any
let sort = (f.extensions || []).filter(function(itm){
return itm.name === 'sort';
});
if(sort.length === 1){
sort[0].async_sort = true;
sort[0].trigger_ids = sortTriggers;
}
//Cols generation for all browsers excepted IE<=7
this.tblHasColTag = Dom.tag(tbl, 'col').length > 0 ? true : false;
//Col elements are enough to keep column widths after sorting and
//filtering
this.setColumnElements();
let createColTags = function(){
for(let k=(tf.nbCells-1); k>=0; k--){
let col = Dom.create('col', ['id', tf.id+'_col_'+k]);
tbl.insertBefore(col, tbl.firstChild);
col.style.width = tf.colWidths[k];
this.gridColElms[k] = col;
}
this.tblHasColTag = true;
};
if (tf.popupFilters) {
filtersRow.style.display = NONE;
if(!this.tblHasColTag){
createColTags.call(this);
} else {
let cols = Dom.tag(tbl, 'col');
for(let ii=0; ii<tf.nbCells; ii++){
cols[ii].setAttribute('id', tf.id+'_col_'+ii);
cols[ii].style.width = tf.colWidths[ii];
this.gridColElms.push(cols[ii]);
}
}
let afterColResizedFn = Types.isFn(f.on_after_col_resized) ?
f.on_after_col_resized : null;
f.on_after_col_resized = function(o, colIndex){
if(!colIndex){
return;
}
let w = o.crWColsRow.cells[colIndex].style.width;
let col = o.gridColElms[colIndex];
col.style.width = w;
let thCW = o.crWColsRow.cells[colIndex].clientWidth;
let tdCW = o.crWRowDataTbl.cells[colIndex].clientWidth;
if(thCW != tdCW){
o.headTbl.style.width = tbl.clientWidth+'px';
}
if(afterColResizedFn){
afterColResizedFn.call(null, o, colIndex);
}
};
if(tf.popupFilters){
filtersRow.style.display = 'none';
}
if(tbl.clientWidth !== this.headTbl.clientWidth){
tbl.style.width = this.headTbl.clientWidth+'px';
}
/** @inherited */
this.initialized = true;
}
/**
* Overrides TableFilter instance properties to adjust to grid layout mode
* @private
*/
setOverrides() {
let tf = this.tf;
tf.refRow = 0;
tf.headersRow = 0;
tf.filtersRowIndex = 1;
}
/**
* Set grid-layout default column widths if column widths are not defined
* @private
*/
setDefaultColWidths() {
let tf = this.tf;
if (tf.colWidths.length > 0) {
return;
}
tf.eachCol((k) => {
let colW;
let cell = tf.dom().rows[tf.getHeadersRowIndex()].cells[k];
if (cell.width !== '') {
colW = cell.width;
} else if (cell.style.width !== '') {
colW = parseInt(cell.style.width, 10);
} else {
colW = this.defaultColWidth;
}
tf.colWidths[k] = colW;
});
tf.setColWidths();
}
/**
* Initial table width
* @returns {Number}
* @private
*/
initialTableWidth() {
let tbl = this.tf.dom();
let width; //initial table width
if (tbl.width !== '') {
width = tbl.width;
}
else if (tbl.style.width !== '') {
width = tbl.style.width;
} else {
width = tbl.clientWidth;
}
return parseInt(width, 10);
}
/**
* Creates container element
* @param {String} tag Tag name
* @param {String} className Css class to assign to element
* @returns {DOMElement}
* @private
*/
createContainer(tag, className) {
let element = createElm(tag);
element.className = className;
return element;
}
/**
* Creates filters row with cells
* @returns {HTMLTableRowElement}
* @private
*/
createFiltersRow() {
let tf = this.tf;
let filtersRow = createElm('tr');
if (this.filters && tf.fltGrid) {
tf.externalFltIds = [];
tf.eachCol((j) => {
let fltTdId = `${tf.prfxFlt + j + this.prfxGridFltTd + tf.id}`;
let cl = createElm(tf.fltCellTag, ['id', fltTdId]);
filtersRow.appendChild(cl);
tf.externalFltIds[j] = fltTdId;
});
}
return filtersRow;
}
/**
* Generates column elements if necessary and assigns their widths
* @private
*/
setColumnElements() {
let tf = this.tf;
let cols = tag(tf.dom(), 'col');
this.tblHasColTag = cols.length > 0;
for (let k = (tf.getCellsNb() - 1); k >= 0; k--) {
let col;
if (!this.tblHasColTag) {
col = createElm('col');
tf.dom().insertBefore(col, tf.dom().firstChild);
} else {
col = cols[k];
}
col.style.width = tf.colWidths[k];
this.colElms[k] = col;
}
this.tblHasColTag = true;
}
/**
* Sets headers row in headers table
* @param {HTMLHeadElement} tableHead Table head element
* @private
*/
setHeadersRow(tableHead) {
if (this.noHeaders) {
// Handle table with no headers, assuming here headers do not
// exist
tableHead.appendChild(createElm('tr'));
} else {
// Headers row are moved from content table to headers table
for (let i = 0; i < this.headRows.length; i++) {
let row = this.tf.dom().rows[this.headRows[i]];
tableHead.appendChild(row);
}
}
}
/**
* Sets width defined in configuration to passed element
* @param {DOMElement} element DOM element
* @private
*/
setConfigWidth(element) {
if (!this.width) {
return;
}
if (this.width.indexOf('%') !== -1) {
element.style.width = '100%';
} else {
element.style.width = this.width;
}
}
/**
* Returns a list of header IDs used for specifing external sort triggers
* @param {HTMLTableRowElement} row DOM row element
* @returns {Array} List of IDs
* @private
*/
getSortTriggerIds(row) {
let tf = this.tf;
let sortTriggers = [];
tf.eachCol((n) => {
let c = row.cells[n];
let thId = c.getAttribute('id');
if (!thId || thId === '') {
thId = `${this.prfxGridTh + n}_${tf.id}`;
c.setAttribute('id', thId);
}
sortTriggers.push(thId);
});
return sortTriggers;
}
/**
* Removes the grid layout
*/
destroy() {
destroy(){
let tf = this.tf;
let tbl = tf.dom();
let tbl = tf.tbl;
if (!this.initialized) {
if(!this.initialized){
return;
}
let t = removeElm(tbl);
let t = Dom.remove(tbl);
this.tblMainCont.parentNode.insertBefore(t, this.tblMainCont);
removeElm(this.tblMainCont);
Dom.remove(this.tblMainCont);
this.tblMainCont = null;
this.headTblCont = null;
@ -476,7 +333,7 @@ export class GridLayout extends Feature {
tbl.outerHTML = this.sourceTblHtml;
//needed to keep reference of table element for future usage
this.tf.tbl = elm(tf.id);
this.tf.tbl = Dom.id(tf.id);
this.initialized = false;
}

View file

@ -1,126 +0,0 @@
import {addEvt, removeEvt} from '../event';
import {root} from '../root';
const JSON = root.JSON;
const location = root.location;
const decodeURIComponent = root.decodeURIComponent;
const encodeURIComponent = root.encodeURIComponent;
/**
* Checks if browser has onhashchange event
*/
export const hasHashChange = () => {
let docMode = root.documentMode;
return ('onhashchange' in root) && (docMode === undefined || docMode > 7);
};
/**
* Manages state via URL hash changes
*
* @export
* @class Hash
*/
export class Hash {
/**
* Creates an instance of Hash
*
* @param {State} state Instance of State
*/
constructor(state) {
/**
* State object
* @type {State}
*/
this.state = state;
/**
* Cached URL hash
* @type {String} Hash string
* @private
*/
this.lastHash = null;
/**
* Application event emitter instance
* @type {Emitter}
*/
this.emitter = state.emitter;
/**
* Bound sync wrapper for future use
* @private
*/
this.boundSync = null;
}
/**
* Initializes the Hash object
*/
init() {
if (!hasHashChange()) {
return;
}
this.lastHash = location.hash;
//Store a bound sync wrapper
this.boundSync = this.sync.bind(this);
this.emitter.on(['state-changed'], (tf, state) => this.update(state));
this.emitter.on(['initialized'], this.boundSync);
addEvt(root, 'hashchange', this.boundSync);
}
/**
* Updates the URL hash based on a state change
*
* @param {State} state Instance of State
*/
update(state) {
let hash = `#${encodeURIComponent(JSON.stringify(state))}`;
if (this.lastHash === hash) {
return;
}
location.hash = hash;
this.lastHash = hash;
}
/**
* Converts a URL hash into a JSON object
*
* @param {String} hash URL hash fragment
* @returns {Object} JSON object
*/
parse(hash) {
if (hash.indexOf('#') === -1) {
return null;
}
hash = hash.substr(1);
return JSON.parse(decodeURIComponent(hash));
}
/**
* Applies current hash state to features
*/
sync() {
let state = this.parse(location.hash);
if (!state) {
return;
}
// override current state with persisted one and sync features
this.state.overrideAndSync(state);
}
/**
* Release Hash event subscriptions and clear fields
*/
destroy() {
this.emitter.off(['state-changed'], (tf, state) => this.update(state));
this.emitter.off(['initialized'], this.boundSync);
removeEvt(root, 'hashchange', this.boundSync);
this.state = null;
this.lastHash = null;
this.emitter = null;
}
}

View file

@ -1,264 +1,155 @@
import {Feature} from '../feature';
import {createElm, createText, elm, removeElm} from '../dom';
import {addEvt, targetEvt, removeEvt} from '../event';
import {NONE} from '../const';
import {root} from '../root';
import {isEmpty, isNull} from '../types';
import {defaultsStr, defaultsNb} from '../settings';
import {RIGHT} from './toolbar';
import {Feature} from './feature';
import Dom from '../dom';
import Event from '../event';
const WIKI_URL = 'https://github.com/koalyptus/TableFilter/wiki/' +
'4.-Filter-operators';
const WEBSITE_URL = 'https://www.tablefilter.com/';
'4.-Filter-operators';
const WEBSITE_URL = 'http://koalyptus.github.io/TableFilter/';
/**
* Help UI component
*/
export class Help extends Feature {
export class Help extends Feature{
/**
* Creates an instance of Help
* @param {TableFilter} tf TableFilter instance
* Help UI component
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, Help);
constructor(tf){
super(tf, 'help');
let f = this.config.help_instructions || {};
var f = this.config;
/**
* ID of main custom container element
* @type {String}
*/
this.tgtId = defaultsStr(f.target_id, null);
/**
* ID of custom container element for instructions
* @type {String}
*/
this.contTgtId = defaultsStr(f.container_target_id, null);
/**
* Instructions text (accepts HTML)
* @type {String}
*/
this.instrText = !isEmpty(f.text) ? f.text :
//id of custom container element for instructions
this.tgtId = f.help_instructions_target_id || null;
//id of custom container element for instructions
this.contTgtId = f.help_instructions_container_target_id ||
null;
//defines help text
this.instrText = f.help_instructions_text ?
f.help_instructions_text :
'Use the filters above each column to filter and limit table ' +
'data. Advanced searches can be performed by using the following ' +
'operators: <br /><b>&lt;</b>, <b>&lt;=</b>, <b>&gt;</b>, ' +
'<b>&gt;=</b>, <b>=</b>, <b>*</b>, <b>!</b>, <b>{</b>, <b>}</b>, ' +
'<b>||</b>,<b>&amp;&amp;</b>, <b>[empty]</b>, <b>[nonempty]</b>, ' +
'<b>rgx:</b><br/><a href="' + WIKI_URL + '" target="_blank">' +
'<b>rgx:</b><br/><a href="'+ WIKI_URL +'" target="_blank">' +
'Learn more</a><hr/>';
/**
* Instructions HTML
* @type {String}
*/
this.instrHtml = defaultsStr(f.html, null);
/**
* Help button text ('?')
* @type {String}
*/
this.btnText = defaultsStr(f.btn_text, '?');
/**
* Custom help button HTML
* @type {String}
*/
this.btnHtml = defaultsStr(f.btn_html, null);
/**
* Css class for help button
* @type {String}
*/
this.btnCssClass = defaultsStr(f.btn_css_class, 'helpBtn');
/**
* Css class for help container element
* @type {String}
*/
this.contCssClass = defaultsStr(f.container_css_class, 'helpCont');
/**
* Button DOM element
* @type {DOMElement}
*/
//defines help innerHtml
this.instrHtml = f.help_instructions_html || null;
//defines reset button text
this.btnText = f.help_instructions_btn_text || '?';
//defines reset button innerHtml
this.btnHtml = f.help_instructions_btn_html || null;
//defines css class for help button
this.btnCssClass = f.help_instructions_btn_css_class || 'helpBtn';
//defines css class for help container
this.contCssClass = f.help_instructions_container_css_class ||
'helpCont';
//help button element
this.btn = null;
/**
* Help container DOM element
* @type {DOMElement}
*/
//help content div
this.cont = null;
/**
* Adjust container left position when table's horizontal scroll is
* on, typically when `responsive` option is enabled.
* @type {Number}
* @defaultValue 25
*/
this.contAdjustLeftPosition =
defaultsNb(f.container_adjust_left_position, 25);
/**
* Bound mouseup wrapper
* @private
*/
this.boundMouseup = null;
/**
* Default HTML appended to instructions text
* @type {String}
*/
this.defaultHtml = '<div class="helpFooter"><h4>TableFilter ' +
'v' + tf.version + '</h4>' + '<a href="' + WEBSITE_URL +
'" target="_blank">' + WEBSITE_URL + '</a>' +
'<br/><span>&copy;2015-' + tf.year + ' {AUTHOR}</span>' +
'v'+ tf.version +'</h4>' +
'<a href="'+ WEBSITE_URL +'" target="_blank">'+ WEBSITE_URL +'</a>'+
'<br/><span>&copy;2015-'+ tf.year +' {AUTHOR}</span>' +
'<div align="center" style="margin-top:8px;">' +
'<a href="javascript:void(0);" class="close">Close</a></div></div>';
/**
* Default position in toolbar ('left'|'center'|'right')
* @type {String}
*/
this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);
//id prefix for help elements
this.prfxHelpSpan = 'helpSpan_';
//id prefix for help elements
this.prfxHelpDiv = 'helpDiv_';
this.emitter.on(['init-help'], () => this.init());
this.emitter.on(['init-help'], ()=> this.init());
}
/**
* Mouse-up event handler handling popup auto-close behaviour
* @private
*/
onMouseup(evt) {
let targetElm = targetEvt(evt);
while (targetElm && targetElm !== this.cont && targetElm !== this.btn) {
targetElm = targetElm.parentNode;
}
if (targetElm !== this.cont && targetElm !== this.btn) {
this.toggle();
}
return;
}
/**
* Initialise Help instance
*/
init() {
if (this.initialized) {
init(){
if(this.initialized){
return;
}
this.emitter.emit('initializing-feature', this, !isNull(this.tgtId));
var tf = this.tf;
let tf = this.tf;
let btn = createElm('span');
let cont = createElm('div');
this.boundMouseup = this.onMouseup.bind(this);
var helpspan = Dom.create('span', ['id', this.prfxHelpSpan+tf.id]);
var helpdiv = Dom.create('div', ['id', this.prfxHelpDiv+tf.id]);
//help button is added to defined element
let targetEl = !this.tgtId ?
tf.feature('toolbar').container(this.toolbarPosition) :
elm(this.tgtId);
targetEl.appendChild(btn);
if(!this.tgtId){
tf.setToolbar();
}
var targetEl = !this.tgtId ? tf.rDiv : Dom.id(this.tgtId);
targetEl.appendChild(helpspan);
let divContainer = !this.contTgtId ? btn : elm(this.contTgtId);
var divContainer = !this.contTgtId ? helpspan : Dom.id(this.contTgtId);
if (!this.btnHtml) {
divContainer.appendChild(cont);
let helplink = createElm('a', ['href', 'javascript:void(0);']);
if(!this.btnHtml){
divContainer.appendChild(helpdiv);
var helplink = Dom.create('a', ['href', 'javascript:void(0);']);
helplink.className = this.btnCssClass;
helplink.appendChild(createText(this.btnText));
btn.appendChild(helplink);
addEvt(helplink, 'click', () => this.toggle());
helplink.appendChild(Dom.text(this.btnText));
helpspan.appendChild(helplink);
Event.add(helplink, 'click', () => { this.toggle(); });
} else {
btn.innerHTML = this.btnHtml;
let helpEl = btn.firstChild;
addEvt(helpEl, 'click', () => this.toggle());
divContainer.appendChild(cont);
helpspan.innerHTML = this.btnHtml;
var helpEl = helpspan.firstChild;
Event.add(helpEl, 'click', () => { this.toggle(); });
divContainer.appendChild(helpdiv);
}
if (!this.instrHtml) {
cont.innerHTML = this.instrText;
cont.className = this.contCssClass;
if(!this.instrHtml){
helpdiv.innerHTML = this.instrText;
helpdiv.className = this.contCssClass;
Event.add(helpdiv, 'dblclick', () => { this.toggle(); });
} else {
if (this.contTgtId) {
divContainer.appendChild(cont);
if(this.contTgtId){
divContainer.appendChild(helpdiv);
}
cont.innerHTML = this.instrHtml;
if (!this.contTgtId) {
cont.className = this.contCssClass;
helpdiv.innerHTML = this.instrHtml;
if(!this.contTgtId){
helpdiv.className = this.contCssClass;
Event.add(helpdiv, 'dblclick', () => { this.toggle(); });
}
}
cont.innerHTML += this.defaultHtml;
addEvt(cont, 'click', () => this.toggle());
helpdiv.innerHTML += this.defaultHtml;
Event.add(helpdiv, 'click', () => { this.toggle(); });
this.cont = cont;
this.btn = btn;
/** @inherited */
this.cont = helpdiv;
this.btn = helpspan;
this.initialized = true;
this.emitter.emit('feature-initialized', this);
}
/**
* Toggle help pop-up
*/
toggle() {
// check only if explicitily disabled as in this case undefined
toggle(){
// check only if explicitily set to false as in this case undefined
// signifies the help feature is enabled by default
if (!this.isEnabled()) {
if(this.enabled === false){
return;
}
// ensure mouseup event handler is removed
removeEvt(root, 'mouseup', this.boundMouseup);
let divDisplay = this.cont.style.display;
if (divDisplay === '' || divDisplay === NONE) {
var divDisplay = this.cont.style.display;
if(divDisplay === '' || divDisplay === 'none'){
this.cont.style.display = 'inline';
// if table element has an horizontal scrollbar adjust container
// left position accordingly
if (this.tf.dom().scrollLeft > 0) {
this.cont.style.left = `${
this.btn.offsetLeft
- this.tf.dom().scrollLeft
+ this.contAdjustLeftPosition
}px`;
}
addEvt(root, 'mouseup', this.boundMouseup);
} else {
this.cont.style.display = NONE;
this.cont.style.left = '';
this.cont.style.display = 'none';
}
}
/**
* Remove help UI
*/
destroy() {
if (!this.initialized) {
destroy(){
if(!this.initialized){
return;
}
removeElm(this.btn);
Dom.remove(this.btn);
this.btn = null;
removeElm(this.cont);
if(!this.cont){
return;
}
Dom.remove(this.cont);
this.cont = null;
this.boundMouseup = null;
this.initialized = false;
}
}
// TODO: remove as soon as feature name is fixed
Help.meta = {alwaysInstantiate: true};

View file

@ -1,94 +1,72 @@
import {createText, createElm, getText} from '../dom';
import {isNull} from '../types';
import {rgxEsc} from '../string';
import {defaultsStr} from '../settings';
import Dom from '../dom';
import Str from '../string';
import Types from '../types';
/**
* Highlight matched keywords upon filtering
*
* @export
* @class HighlightKeyword
*/
export class HighlightKeyword {
export class HighlightKeyword{
/**
* Creates an instance of HighlightKeyword
* @param {TableFilter} tf TableFilter instance
* HighlightKeyword, highlight matched keyword
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
let f = tf.config();
var f = tf.config();
//defines css class for highlighting
this.highlightCssClass = f.highlight_css_class || 'keyword';
this.highlightedNodes = [];
/**
* Css class for highlighted term
* @type {String}
*/
this.highlightCssClass = defaultsStr(f.highlight_css_class, 'keyword');
/**
* TableFilter instance
* @type {TableFilter}
*/
this.tf = tf;
/**
* TableFilter's emitter instance
* @type {Emitter}
*/
this.emitter = tf.emitter;
}
/**
* Initializes HighlightKeyword instance
*/
init() {
init(){
this.emitter.on(
['before-filtering', 'destroy'],
() => this.unhighlightAll()
()=> this.unhighlightAll()
);
this.emitter.on(
['highlight-keyword'],
(tf, cell, term) => this._processTerm(cell, term)
(tf, cell, word)=>
this.highlight(cell, word, this.highlightCssClass)
);
}
/**
* Highlight occurences of searched term in passed node
* highlight occurences of searched term in passed node
* @param {Node} node
* @param {String} term Searched term
* @param {String} word Searched term
* @param {String} cssClass Css class name
*
* TODO: refactor this method
*/
highlight(node, term, cssClass) {
highlight(node, word, cssClass){
// Iterate into this nodes childNodes
if (node.hasChildNodes) {
let children = node.childNodes;
for (let i = 0; i < children.length; i++) {
this.highlight(children[i], term, cssClass);
if(node.hasChildNodes){
var children = node.childNodes;
for(var i=0; i<children.length; i++){
this.highlight(children[i], word, cssClass);
}
}
if (node.nodeType === 3) {
let nodeVal = node.nodeValue.toLowerCase();
let termIdx = nodeVal.indexOf(term.toLowerCase());
if (termIdx !== -1) {
let pn = node.parentNode;
if (pn && pn.className !== cssClass) {
// term not highlighted yet
let nv = node.nodeValue,
if(node.nodeType === 3){
var tempNodeVal = Str.lower(node.nodeValue);
var tempWordVal = Str.lower(word);
if(tempNodeVal.indexOf(tempWordVal) != -1){
var pn = node.parentNode;
if(pn && pn.className != cssClass){
// word not highlighted yet
var nv = node.nodeValue,
ni = tempNodeVal.indexOf(tempWordVal),
// Create a load of replacement nodes
before = createText(nv.substr(0, termIdx)),
value = nv.substr(termIdx, term.length),
after = createText(nv.substr(termIdx + term.length)),
text = createText(value),
container = createElm('span');
container.className = cssClass;
container.appendChild(text);
pn.insertBefore(before, node);
pn.insertBefore(container, node);
pn.insertBefore(after, node);
before = Dom.text(nv.substr(0, ni)),
docWordVal = nv.substr(ni,word.length),
after = Dom.text(nv.substr(ni+word.length)),
hiwordtext = Dom.text(docWordVal),
hiword = Dom.create('span');
hiword.className = cssClass;
hiword.appendChild(hiwordtext);
pn.insertBefore(before,node);
pn.insertBefore(hiword,node);
pn.insertBefore(after,node);
pn.removeChild(node);
this.highlightedNodes.push(hiword.firstChild);
}
}
}
@ -96,85 +74,66 @@ export class HighlightKeyword {
/**
* Removes highlight to nodes matching passed string
* @param {String} term
* @param {String} word
* @param {String} cssClass Css class to remove
*/
unhighlight(term, cssClass) {
let highlightedNodes = this.tf.dom().querySelectorAll(`.${cssClass}`);
for (let i = 0; i < highlightedNodes.length; i++) {
let n = highlightedNodes[i];
let nodeVal = getText(n);
if (isNull(term) ||
nodeVal.toLowerCase().indexOf(term.toLowerCase()) !== -1) {
let parentNode = n.parentNode;
parentNode.replaceChild(createText(nodeVal), n);
parentNode.normalize();
unhighlight(word, cssClass){
var arrRemove = [];
var highlightedNodes = this.highlightedNodes;
for(var i=0; i<highlightedNodes.length; i++){
var n = highlightedNodes[i];
if(!n){
continue;
}
var tempNodeVal = Str.lower(n.nodeValue),
tempWordVal = Str.lower(word);
if(tempNodeVal.indexOf(tempWordVal) !== -1){
var pn = n.parentNode;
if(pn && pn.className === cssClass){
var prevSib = pn.previousSibling,
nextSib = pn.nextSibling;
if(!prevSib || !nextSib){ continue; }
nextSib.nodeValue = prevSib.nodeValue + n.nodeValue +
nextSib.nodeValue;
prevSib.nodeValue = '';
n.nodeValue = '';
arrRemove.push(i);
}
}
}
for(var k=0; k<arrRemove.length; k++){
highlightedNodes.splice(arrRemove[k], 1);
}
}
/**
* Clear all occurrences of highlighted nodes
*/
unhighlightAll() {
if (!this.tf.highlightKeywords) {
unhighlightAll(){
if(!this.tf.highlightKeywords){
return;
}
this.unhighlight(null, this.highlightCssClass);
// iterate filters values to unhighlight all values
this.tf.getFiltersValue().forEach((val)=> {
if(Types.isArray(val)){
val.forEach((item)=>
this.unhighlight(item, this.highlightCssClass));
} else {
this.unhighlight(val, this.highlightCssClass);
}
});
this.highlightedNodes = [];
}
/** Remove feature */
destroy() {
destroy(){
this.emitter.off(
['before-filtering', 'destroy'],
() => this.unhighlightAll()
()=> this.unhighlightAll()
);
this.emitter.off(
['highlight-keyword'],
(tf, cell, term) => this._processTerm(cell, term)
(tf, cell, word)=>
this.highlight(cell, word, this.highlightCssClass)
);
}
/**
* Ensure filtering operators are handled before highlighting any match
* @param {any} Table cell to look searched term into
* @param {any} Searched termIdx
*/
_processTerm(cell, term) {
let tf = this.tf;
let reLk = new RegExp(rgxEsc(tf.lkOperator));
let reEq = new RegExp(tf.eqOperator);
let reSt = new RegExp(tf.stOperator);
let reEn = new RegExp(tf.enOperator);
let reLe = new RegExp(tf.leOperator);
let reGe = new RegExp(tf.geOperator);
let reL = new RegExp(tf.lwOperator);
let reG = new RegExp(tf.grOperator);
let reD = new RegExp(tf.dfOperator);
term = term
.replace(reLk, '')
.replace(reEq, '')
.replace(reSt, '')
.replace(reEn, '');
if (reLe.test(term) || reGe.test(term) || reL.test(term) ||
reG.test(term) || reD.test(term)) {
term = getText(cell);
}
if (term === '') {
return;
}
this.highlight(cell, term, this.highlightCssClass);
}
}
// TODO: remove as soon as feature name is fixed
HighlightKeyword.meta = {
name: 'highlightKeyword',
altName: 'highlightKeywords'
};
}

View file

@ -1,182 +1,160 @@
import {Feature} from '../feature';
import {createElm, createText, elm, removeElm} from '../dom';
import {EMPTY_FN} from '../types';
import {root} from '../root';
import {NONE} from '../const';
import {defaultsStr, defaultsFn} from '../settings';
import {Feature} from './feature';
import Dom from '../dom';
import Types from '../types';
const BEFORE_ACTION_EVENTS = [
'before-filtering',
'before-populating-filter',
'before-page-change',
'before-clearing-filters',
'before-page-length-change',
'before-reset-page',
'before-reset-page-length',
'before-loading-extensions',
'before-loading-themes'
];
let global = window;
const AFTER_ACTION_EVENTS = [
'after-filtering',
'after-populating-filter',
'after-page-change',
'after-clearing-filters',
'after-page-length-change',
'after-reset-page',
'after-reset-page-length',
'after-loading-extensions',
'after-loading-themes'
];
/**
* Activity indicator
*
* @export
* @class Loader
* @extends {Feature}
*/
export class Loader extends Feature {
export class Loader extends Feature{
/**
* Creates an instance of Loader.
*
* @param {TableFilter} tf TableFilter instance
* Loading message/spinner
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, Loader);
super(tf, 'loader');
let f = this.config.loader || {};
// TableFilter configuration
let f = this.config;
/**
* ID of custom container element
* @type {String}
*/
this.targetId = defaultsStr(f.target_id, null);
/**
* Loader container DOM element
* @type {DOMElement}
*/
this.cont = null;
/**
* Text displayed when indicator is visible
* @type {String}
*/
this.text = defaultsStr(f.text, 'Loading...');
/**
* Custom HTML injected in Loader's container element
* @type {String}
*/
this.html = defaultsStr(f.html, null);
/**
* Css class for Loader's container element
* @type {String}
*/
this.cssClass = defaultsStr(f.css_class, 'loader');
/**
* Close delay in milliseconds
* @type {Number}
*/
this.closeDelay = 250;
/**
* Callback fired when loader is displayed
* @type {Function}
*/
this.onShow = defaultsFn(f.on_show_loader, EMPTY_FN);
/**
* Callback fired when loader is closed
* @type {Function}
*/
this.onHide = defaultsFn(f.on_hide_loader, EMPTY_FN);
//id of container element
this.loaderTgtId = f.loader_target_id || null;
//div containing loader
this.loaderDiv = null;
//defines loader text
this.loaderText = f.loader_text || 'Loading...';
//defines loader innerHtml
this.loaderHtml = f.loader_html || null;
//defines css class for loader div
this.loaderCssClass = f.loader_css_class || 'loader';
//delay for hiding loader
this.loaderCloseDelay = 250;
//callback function before loader is displayed
this.onShowLoader = Types.isFn(f.on_show_loader) ?
f.on_show_loader : null;
//callback function after loader is closed
this.onHideLoader = Types.isFn(f.on_hide_loader) ?
f.on_hide_loader : null;
//loader div
this.prfxLoader = 'load_';
}
/**
* Initializes Loader instance
*/
init() {
if (this.initialized) {
if(this.initialized){
return;
}
let tf = this.tf;
let emitter = this.emitter;
let containerDiv = createElm('div');
containerDiv.className = this.cssClass;
let containerDiv = Dom.create('div', ['id', this.prfxLoader+tf.id]);
containerDiv.className = this.loaderCssClass;
let targetEl = !this.targetId ?
tf.dom().parentNode : elm(this.targetId);
if (!this.targetId) {
targetEl.insertBefore(containerDiv, tf.dom());
let targetEl = !this.loaderTgtId ?
tf.tbl.parentNode : Dom.id(this.loaderTgtId);
if(!this.loaderTgtId){
targetEl.insertBefore(containerDiv, tf.tbl);
} else {
targetEl.appendChild(containerDiv);
}
this.cont = containerDiv;
if (!this.html) {
this.cont.appendChild(createText(this.text));
this.loaderDiv = containerDiv;
if(!this.loaderHtml){
this.loaderDiv.appendChild(Dom.text(this.loaderText));
} else {
this.cont.innerHTML = this.html;
this.loaderDiv.innerHTML = this.loaderHtml;
}
this.show(NONE);
this.show('none');
// Subscribe to events
emitter.on(BEFORE_ACTION_EVENTS, () => this.show(''));
emitter.on(AFTER_ACTION_EVENTS, () => this.show(NONE));
emitter.on([
'before-filtering',
'before-populating-filter',
'before-changing-page',
'before-clearing-filters',
'before-changing-results-per-page',
'before-reset-page',
'before-reset-page-length',
'before-loading-extensions',
'before-loading-themes'
],
()=> this.show('')
);
emitter.on([
'after-filtering',
'after-populating-filter',
'after-changing-page',
'after-clearing-filters',
'after-changing-results-per-page',
'after-reset-page',
'after-reset-page-length',
'after-loading-extensions',
'after-loading-themes'
],
()=> this.show('none')
);
/** @inherited */
this.initialized = true;
}
/**
* Shows or hides activity indicator
* @param {String} Two possible values: '' or 'none'
*/
show(p) {
if (!this.isEnabled()) {
if(!this.isEnabled() /*|| this.loaderDiv.style.display === p*/){
return;
}
function displayLoader() {
if (!this.cont) {
let displayLoader = () => {
if(!this.loaderDiv){
return;
}
if (p !== NONE) {
this.onShow(this);
if(this.onShowLoader && p !== 'none'){
this.onShowLoader.call(null, this);
}
this.cont.style.display = p;
if (p === NONE) {
this.onHide(this);
this.loaderDiv.style.display = p;
if(this.onHideLoader && p === 'none'){
this.onHideLoader.call(null, this);
}
};
let t = p === NONE ? this.closeDelay : 1;
root.setTimeout(displayLoader.bind(this), t);
let t = p === 'none' ? this.loaderCloseDelay : 1;
global.setTimeout(displayLoader, t);
}
/**
* Removes feature
*/
destroy() {
if (!this.initialized) {
if(!this.initialized){
return;
}
let emitter = this.emitter;
removeElm(this.cont);
this.cont = null;
Dom.remove(this.loaderDiv);
this.loaderDiv = null;
// Unsubscribe to events
emitter.off(BEFORE_ACTION_EVENTS, () => this.show(''));
emitter.off(AFTER_ACTION_EVENTS, () => this.show(NONE));
emitter.off([
'before-filtering',
'before-populating-filter',
'before-changing-page',
'before-clearing-filters',
'before-changing-results-per-page',
'before-reset-page',
'before-reset-page-length',
'before-loading-extensions',
'before-loading-themes'
],
()=> this.show('')
);
emitter.off([
'after-filtering',
'after-populating-filter',
'after-changing-page',
'after-clearing-filters',
'after-changing-results-per-page',
'after-reset-page',
'after-reset-page-length',
'after-loading-extensions',
'after-loading-themes'
],
()=> this.show('none')
);
this.initialized = false;
}

View file

@ -1,146 +0,0 @@
import {Feature} from '../feature';
import {addClass, removeClass, hasClass} from '../dom';
import {EMPTY_FN} from '../types';
import {defaultsStr, defaultsFn} from '../settings';
/**
* Visual indicator for filtered columns
* @export
* @class MarkActiveColumns
* @extends {Feature}
*/
export class MarkActiveColumns extends Feature {
/**
* Create an instance of MarkActiveColumns
* @param {TableFilter} tf TableFilter instance
*/
constructor(tf) {
super(tf, MarkActiveColumns);
let config = this.config.mark_active_columns || {};
/**
* Css class for filtered (active) columns
* @type {String}
*/
this.headerCssClass = defaultsStr(config.header_css_class,
'activeHeader');
/**
* Css class for filtered (active) column cells
* @type {String}
*/
this.cellCssClass = defaultsStr(config.cell_css_class,
'activeCell');
/**
* Enable/disable column highlighting
* @type {Boolean}
*/
this.highlightColumn = Boolean(config.highlight_column);
/**
* Callback fired before a column is marked as filtered
* @type {Function}
*/
this.onBeforeActiveColumn = defaultsFn(config.on_before_active_column,
EMPTY_FN);
/**
* Callback fired after a column is marked as filtered
* @type {Function}
*/
this.onAfterActiveColumn = defaultsFn(config.on_after_active_column,
EMPTY_FN);
}
/**
* Initialise MarkActiveColumns instance
*/
init() {
if (this.initialized) {
return;
}
this.emitter.on(['before-filtering'], () => this.clearActiveColumns());
this.emitter.on(
['cell-processed'],
(tf, colIndex) => this.markActiveColumn(colIndex)
);
/** @inherited */
this.initialized = true;
}
/**
* Clear filtered columns visual indicator (background color)
*/
clearActiveColumns() {
let tf = this.tf;
tf.eachCol((idx) => {
removeClass(tf.getHeaderElement(idx), this.headerCssClass);
if (this.highlightColumn) {
this.eachColumnCell(idx,
(cell) => removeClass(cell, this.cellCssClass));
}
});
}
/**
* Mark currently filtered column
* @param {Number} colIndex Column index
*/
markActiveColumn(colIndex) {
let tf = this.tf;
let header = tf.getHeaderElement(colIndex);
if (hasClass(header, this.headerCssClass)) {
return;
}
this.onBeforeActiveColumn(this, colIndex);
addClass(header, this.headerCssClass);
if (this.highlightColumn) {
this.eachColumnCell(colIndex,
(cell) => addClass(cell, this.cellCssClass));
}
this.onAfterActiveColumn(this, colIndex);
}
/**
* Column cells iterator
* TODO: make public and move into TableFilter if used elsewhere
* @param {Number} colIndex
* @param {Function} fn
* @param {DOMElement} tbl
* @private
*/
eachColumnCell(colIndex, fn = EMPTY_FN, tbl = this.tf.dom()) {
// TODO: remove [].forEach when polyfill for PhanthomJs is available
[].forEach.call(
tbl.querySelectorAll(`tbody td:nth-child(${colIndex + 1})`), fn);
}
/**
* Remove feature
*/
destroy() {
if (!this.initialized) {
return;
}
this.clearActiveColumns();
this.emitter.off(['before-filtering'], () => this.clearActiveColumns());
this.emitter.off(
['cell-processed'],
(tf, colIndex) => this.markActiveColumn(colIndex)
);
/** @inherited */
this.initialized = false;
}
}

View file

@ -1,107 +1,58 @@
import {Feature} from '../feature';
import {createElm, elm, removeElm} from '../dom';
import {isEmpty, EMPTY_FN} from '../types';
import {NONE} from '../const';
import {defaultsStr, defaultsFn} from '../settings';
import {Feature} from './feature';
import Dom from '../dom';
import Types from '../types';
/**
* UI when filtering yields no matches
* @export
* @class NoResults
* @extends {Feature}
*/
export class NoResults extends Feature {
export class NoResults extends Feature{
/**
* Creates an instance of NoResults
* @param {TableFilter} tf TableFilter instance
* No results message UI component
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, NoResults);
constructor(tf){
super(tf, 'noResults');
//configuration object
let f = this.config.no_results_message || {};
let f = this.config.no_results_message;
/**
* Text (accepts HTML)
* @type {String}
*/
this.content = defaultsStr(f.content, 'No results');
this.content = f.content || 'No results';
this.customContainer = f.custom_container || null;
this.customContainerId = f.custom_container_id || null;
this.isExternal = !Types.isEmpty(this.customContainer) ||
!Types.isEmpty(this.customContainerId);
this.cssClass = f.css_class || 'no-results';
/**
* Custom container DOM element
* @type {DOMElement}
*/
this.customContainer = defaultsStr(f.custom_container, null);
/**
* ID of custom container element
* @type {String}
*/
this.customContainerId = defaultsStr(f.custom_container_id, null);
/**
* Indicates if UI is contained in a external element
* @type {Boolean}
* @private
*/
this.isExternal = !isEmpty(this.customContainer) ||
!isEmpty(this.customContainerId);
/**
* Css class assigned to container element
* @type {String}
*/
this.cssClass = defaultsStr(f.css_class, 'no-results');
/**
* Stores container DOM element
* @type {DOMElement}
*/
this.cont = null;
/**
* Callback fired before the message is displayed
* @type {Function}
*/
this.onBeforeShow = defaultsFn(f.on_before_show_msg, EMPTY_FN);
//callback before message is displayed
this.onBeforeShowMsg = Types.isFn(f.on_before_show_msg) ?
f.on_before_show_msg : null;
//callback after message is displayed
this.onAfterShowMsg = Types.isFn(f.on_after_show_msg) ?
f.on_after_show_msg : null;
//callback before message is hidden
this.onBeforeHideMsg = Types.isFn(f.on_before_hide_msg) ?
f.on_before_hide_msg : null;
//callback after message is hidden
this.onAfterHideMsg = Types.isFn(f.on_after_hide_msg) ?
f.on_after_hide_msg : null;
/**
* Callback fired after the message is displayed
* @type {Function}
*/
this.onAfterShow = defaultsFn(f.on_after_show_msg, EMPTY_FN);
/**
* Callback fired before the message is hidden
* @type {Function}
*/
this.onBeforeHide = defaultsFn(f.on_before_hide_msg, EMPTY_FN);
/**
* Callback fired after the message is hidden
* @type {Function}
*/
this.onAfterHide = defaultsFn(f.on_after_hide_msg, EMPTY_FN);
this.prfxNoResults = 'nores_';
}
/**
* Initializes NoResults instance
*/
init() {
if (this.initialized) {
init(){
if(this.initialized){
return;
}
let tf = this.tf;
let target = this.customContainer || elm(this.customContainerId) ||
tf.dom();
let target = this.customContainer || Dom.id(this.customContainerId) ||
tf.tbl;
//container
let cont = createElm('div');
let cont = Dom.create('div', ['id', this.prfxNoResults+tf.id]);
cont.className = this.cssClass;
cont.innerHTML = this.content;
if (this.isExternal) {
if(this.isExternal){
target.appendChild(cont);
} else {
target.parentNode.insertBefore(cont, target.nextSibling);
@ -110,83 +61,74 @@ export class NoResults extends Feature {
this.cont = cont;
// subscribe to after-filtering event
this.emitter.on(
['initialized', 'after-filtering'],
() => this.toggle()
);
this.emitter.on(['after-filtering'], ()=> this.toggle());
/** @inherited */
this.initialized = true;
this.hide();
}
/**
* Toggle no results message
*/
toggle() {
if (this.tf.getValidRowsNb() > 0) {
toggle(){
if(this.tf.nbVisibleRows > 0){
this.hide();
} else {
this.show();
}
}
/**
* Show no results message
*/
show() {
if (!this.initialized || !this.isEnabled()) {
show(){
if(!this.initialized || !this.isEnabled()){
return;
}
this.onBeforeShow(this.tf, this);
if(this.onBeforeShowMsg){
this.onBeforeShowMsg.call(null, this.tf, this);
}
this.setWidth();
this.cont.style.display = 'block';
this.onAfterShow(this.tf, this);
if(this.onAfterShowMsg){
this.onAfterShowMsg.call(null, this.tf, this);
}
}
/**
* Hide no results message
*/
hide() {
if (!this.initialized || !this.isEnabled()) {
hide(){
if(!this.initialized || !this.isEnabled()){
return;
}
this.onBeforeHide(this.tf, this);
this.cont.style.display = NONE;
if(this.onBeforeHideMsg){
this.onBeforeHideMsg.call(null, this.tf, this);
}
this.onAfterHide(this.tf, this);
this.cont.style.display = 'none';
if(this.onBeforeHideMsg){
this.onBeforeHideMsg.call(null, this.tf, this);
}
}
/**
* Sets no results container width
* @private
*/
setWidth() {
if (!this.initialized || this.isExternal || !this.isEnabled()) {
setWidth(){
if(!this.initialized || this.isExternal || !this.isEnabled()){
return;
}
let tf = this.tf;
if (tf.gridLayout) {
let gridLayout = tf.feature('gridLayout');
this.cont.style.width = gridLayout.headTbl.clientWidth + 'px';
if(this.tf.gridLayout){
let gridLayout = this.tf.feature('gridLayout');
this.cont.style.width = gridLayout.tblCont.clientWidth + 'px';
} else {
this.cont.style.width = (tf.dom().tHead ?
tf.dom().tHead.clientWidth :
tf.dom().tBodies[0].clientWidth) + 'px';
this.cont.style.width = this.tf.tbl.clientWidth + 'px';
}
}
/** Remove feature */
destroy() {
if (!this.initialized) {
destroy(){
if(!this.initialized){
return;
}
removeElm(this.cont);
Dom.remove(this.cont);
this.cont = null;
// unsubscribe to after-filtering event
this.emitter.off(['after-filtering'], () => this.toggle());
this.emitter.off(['after-filtering'], ()=> this.toggle());
this.initialized = false;
}

File diff suppressed because it is too large Load diff

View file

@ -1,276 +1,139 @@
import {Feature} from '../feature';
import {isUndef, EMPTY_FN} from '../types';
import {createElm, removeElm} from '../dom';
import {addEvt, cancelEvt, stopEvt, targetEvt, removeEvt} from '../event';
import {INPUT, NONE, CHECKLIST, MULTIPLE} from '../const';
import {root} from '../root';
import {defaultsStr, defaultsBool, defaultsArr, defaultsFn} from '../settings';
import {Feature} from './feature';
import Types from '../types';
import Dom from '../dom';
import Event from '../event';
/**
* Pop-up filter component
* @export
* @class PopupFilter
* @extends {Feature}
*/
export class PopupFilter extends Feature {
export class PopupFilter extends Feature{
/**
* Creates an instance of PopupFilter
* @param {TableFilter} tf TableFilter instance
* Pop-up filter component
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, PopupFilter);
constructor(tf){
super(tf, 'popupFilters');
// Configuration object
let f = this.config.popup_filters || {};
var f = this.config;
/**
* Close active popup filter upon filtering, enabled by default
* @type {Boolean}
*/
this.closeOnFiltering = defaultsBool(f.close_on_filtering, true);
// Enable external filters
tf.isExternalFlt = true;
tf.externalFltTgtIds = [];
/**
* Filter icon path
* @type {String}
*/
this.iconPath = defaultsStr(f.image, tf.themesPath + 'icn_filter.gif');
//filter icon path
this.popUpImgFlt = f.popup_filters_image ||
tf.themesPath+'icn_filter.gif';
//active filter icon path
this.popUpImgFltActive = f.popup_filters_image_active ||
tf.themesPath+'icn_filterActive.gif';
this.popUpImgFltHtml = f.popup_filters_image_html ||
'<img src="'+ this.popUpImgFlt +'" alt="Column filter" />';
//defines css class for popup div containing filter
this.popUpDivCssClass = f.popup_div_css_class || 'popUpFilter';
//callback function before popup filtes is opened
this.onBeforePopUpOpen = Types.isFn(f.on_before_popup_filter_open) ?
f.on_before_popup_filter_open : null;
//callback function after popup filtes is opened
this.onAfterPopUpOpen = Types.isFn(f.on_after_popup_filter_open) ?
f.on_after_popup_filter_open : null;
//callback function before popup filtes is closed
this.onBeforePopUpClose =
Types.isFn(f.on_before_popup_filter_close) ?
f.on_before_popup_filter_close : null;
//callback function after popup filtes is closed
this.onAfterPopUpClose = Types.isFn(f.on_after_popup_filter_close) ?
f.on_after_popup_filter_close : null;
/**
* Active filter icon path
* @type {string}
*/
this.activeIconPath = defaultsStr(f.image_active,
tf.themesPath + 'icn_filterActive.gif');
//stores filters spans
this.popUpFltSpans = [];
//stores filters icons
this.popUpFltImgs = [];
//stores filters containers
this.popUpFltElms = this.popUpFltElmCache || [];
this.popUpFltAdjustToContainer = true;
/**
* HTML for the filter icon
* @type {string}
*/
this.iconHtml = defaultsStr(f.image_html,
'<img src="' + this.iconPath + '" alt="Column filter" />');
/**
* Css class assigned to the popup container element
* @type {String}
*/
this.placeholderCssClass = defaultsStr(f.placeholder_css_class,
'popUpPlaceholder');
/**
* Css class assigned to filter container element
* @type {String}
*/
this.containerCssClass = defaultsStr(f.div_css_class, 'popUpFilter');
/**
* Ensure filter's container element width matches column width, enabled
* by default
* @type {Boolean}
*/
this.adjustToContainer = defaultsBool(f.adjust_to_container, true);
/**
* Callback fired before a popup filter is opened
* @type {Function}
*/
this.onBeforeOpen = defaultsFn(f.on_before_popup_filter_open, EMPTY_FN);
/**
* Callback fired after a popup filter is opened
* @type {Function}
*/
this.onAfterOpen = defaultsFn(f.on_after_popup_filter_open, EMPTY_FN);
/**
* Callback fired before a popup filter is closed
* @type {Function}
*/
this.onBeforeClose = defaultsFn(f.on_before_popup_filter_close,
EMPTY_FN);
/**
* Callback fired after a popup filter is closed
* @type {Function}
*/
this.onAfterClose = defaultsFn(f.on_after_popup_filter_close, EMPTY_FN);
/**
* Collection of filters spans
* @type {Array}
* @private
*/
this.fltSpans = [];
/**
* Collection of filters icons
* @type {Array}
* @private
*/
this.fltIcons = [];
/**
* Collection of filters icons cached after pop-up filters are removed
* @type {Array}
* @private
*/
this.filtersCache = null;
/**
* Collection of filters containers
* @type {Array}
* @private
*/
this.fltElms = defaultsArr(this.filtersCache, []);
/**
* Prefix for pop-up filter container ID
* @type {String}
* @private
*/
this.prfxDiv = 'popup_';
/**
* Column index of popup filter currently active
* @type {Number}
* @private
*/
this.activeFilterIdx = -1;
//id prefix for pop-up filter span
this.prfxPopUpSpan = 'popUpSpan_';
//id prefix for pop-up div containing filter
this.prfxPopUpDiv = 'popUpDiv_';
}
/**
* Click event handler for pop-up filter icon
* @private
*/
onClick(evt) {
let elm = targetEvt(evt).parentNode;
let colIndex = parseInt(elm.getAttribute('ci'), 10);
onClick(e){
var evt = e || global.event,
elm = evt.target.parentNode,
colIndex = parseInt(elm.getAttribute('ci'), 10);
this.closeAll(colIndex);
this.toggle(colIndex);
if (this.adjustToContainer) {
let cont = this.fltElms[colIndex],
if(this.popUpFltAdjustToContainer){
var popUpDiv = this.popUpFltElms[colIndex],
header = this.tf.getHeaderElement(colIndex),
headerWidth = header.clientWidth * 0.95;
cont.style.width = parseInt(headerWidth, 10) + 'px';
popUpDiv.style.width = parseInt(headerWidth, 10) + 'px';
}
cancelEvt(evt);
stopEvt(evt);
}
/**
* Mouse-up event handler handling popup filter auto-close behaviour
* @private
*/
onMouseup(evt) {
if (this.activeFilterIdx === -1) {
return;
}
let targetElm = targetEvt(evt);
let activeFlt = this.fltElms[this.activeFilterIdx];
let icon = this.fltIcons[this.activeFilterIdx];
if (icon === targetElm) {
return;
}
while (targetElm && targetElm !== activeFlt) {
targetElm = targetElm.parentNode;
}
if (targetElm !== activeFlt) {
this.close(this.activeFilterIdx);
}
return;
Event.cancel(evt);
Event.stop(evt);
}
/**
* Initialize DOM elements
*/
init() {
if (this.initialized) {
init(){
if(this.initialized){
return;
}
let tf = this.tf;
// Enable external filters
tf.externalFltIds = [''];
// Override filters row index supplied by configuration
tf.filtersRowIndex = 0;
var tf = this.tf;
// Override headers row index if no grouped headers
// TODO: Because of the filters row generation, headers row index needs
// adjusting: prevent useless row generation
if (tf.headersRow <= 1 && isNaN(tf.config().headers_row_index)) {
if(tf.headersRow <= 1){
tf.headersRow = 0;
}
// Adjust headers row index for grid-layout mode
// TODO: Because of the filters row generation, headers row index needs
// adjusting: prevent useless row generation
if (tf.gridLayout) {
tf.headersRow--;
this.buildIcons();
for(var i=0; i<tf.nbCells; i++){
if(tf.getFilterType(i) === tf.fltTypeNone){
continue;
}
var popUpSpan = Dom.create(
'span',
['id', this.prfxPopUpSpan+tf.id+'_'+i],
['ci', i]
);
popUpSpan.innerHTML = this.popUpImgFltHtml;
var header = tf.getHeaderElement(i);
header.appendChild(popUpSpan);
Event.add(popUpSpan, 'click', (evt) => { this.onClick(evt); });
this.popUpFltSpans[i] = popUpSpan;
this.popUpFltImgs[i] = popUpSpan.firstChild;
}
// subscribe to events
this.emitter.on(['before-filtering'], () => this.setIconsState());
this.emitter.on(['after-filtering'], () => this.closeAll());
this.emitter.on(['before-filtering'], ()=> this.buildIcons());
this.emitter.on(['after-filtering'], ()=> this.closeAll());
this.emitter.on(['cell-processed'],
(tf, cellIndex) => this.changeState(cellIndex, true));
this.emitter.on(['filters-row-inserted'], () => this.buildIcons());
(tf, cellIndex)=> this.buildIcon(cellIndex, true));
this.emitter.on(['filters-row-inserted'], ()=> this.tf.headersRow++);
this.emitter.on(['before-filter-init'],
(tf, colIndex) => this.build(colIndex));
(tf, colIndex)=> this.build(colIndex));
/** @inherited */
this.initialized = true;
}
/**
* Reset previously destroyed feature
*/
reset() {
reset(){
this.enable();
this.init();
this.buildIcons();
this.buildAll();
}
/**
* Build all filters icons
*/
buildIcons() {
let tf = this.tf;
// TODO: Because of the filters row generation, headers row index needs
// adjusting: prevent useless row generation
tf.headersRow++;
tf.eachCol(
(i) => {
let icon = createElm('span', ['ci', i]);
icon.innerHTML = this.iconHtml;
let header = tf.getHeaderElement(i);
header.appendChild(icon);
addEvt(icon, 'click', (evt) => this.onClick(evt));
this.fltSpans[i] = icon;
this.fltIcons[i] = icon.firstChild;
},
// continue condition function
(i) => tf.getFilterType(i) === NONE
);
}
/**
* Build all pop-up filters elements
*/
buildAll() {
for (let i = 0; i < this.filtersCache.length; i++) {
this.build(i, this.filtersCache[i]);
buildAll(){
for(var i=0; i<this.popUpFltElmCache.length; i++){
this.build(i, this.popUpFltElmCache[i]);
}
}
@ -279,106 +142,69 @@ export class PopupFilter extends Feature {
* @param {Number} colIndex Column index
* @param {Object} div Optional container DOM element
*/
build(colIndex, div) {
let tf = this.tf;
let contId = `${this.prfxDiv}${tf.id}_${colIndex}`;
let placeholder = createElm('div', ['class', this.placeholderCssClass]);
let cont = div ||
createElm('div', ['id', contId], ['class', this.containerCssClass]);
tf.externalFltIds[colIndex] = cont.id;
placeholder.appendChild(cont);
let header = tf.getHeaderElement(colIndex);
header.insertBefore(placeholder, header.firstChild);
addEvt(cont, 'click', (evt) => stopEvt(evt));
this.fltElms[colIndex] = cont;
build(colIndex, div){
var tf = this.tf;
var popUpDiv = !div ?
Dom.create('div', ['id', this.prfxPopUpDiv+tf.id+'_'+colIndex]) :
div;
popUpDiv.className = this.popUpDivCssClass;
tf.externalFltTgtIds.push(popUpDiv.id);
var header = tf.getHeaderElement(colIndex);
header.insertBefore(popUpDiv, header.firstChild);
Event.add(popUpDiv, 'click', (evt) => Event.stop(evt));
this.popUpFltElms[colIndex] = popUpDiv;
}
/**
* Toggle visibility of specified filter
* Toogle visibility of specified filter
* @param {Number} colIndex Column index
*/
toggle(colIndex) {
if (!this.isOpen(colIndex)) {
this.open(colIndex);
toggle(colIndex){
var tf = this.tf,
popUpFltElm = this.popUpFltElms[colIndex];
if(popUpFltElm.style.display === 'none' ||
popUpFltElm.style.display === ''){
if(this.onBeforePopUpOpen){
this.onBeforePopUpOpen.call(
null, this, this.popUpFltElms[colIndex], colIndex);
}
popUpFltElm.style.display = 'block';
if(tf.getFilterType(colIndex) === tf.fltTypeInp){
var flt = tf.getFilterElement(colIndex);
if(flt){
flt.focus();
}
}
if(this.onAfterPopUpOpen){
this.onAfterPopUpOpen.call(
null, this, this.popUpFltElms[colIndex], colIndex);
}
} else {
this.close(colIndex);
}
}
/**
* Open popup filter of specified column
* @param {Number} colIndex Column index
*/
open(colIndex) {
let tf = this.tf,
container = this.fltElms[colIndex];
this.onBeforeOpen(this, container, colIndex);
container.style.display = 'block';
this.activeFilterIdx = colIndex;
addEvt(root, 'mouseup', (evt) => this.onMouseup(evt));
if (tf.getFilterType(colIndex) === INPUT) {
let flt = tf.getFilterElement(colIndex);
if (flt) {
flt.focus();
if(this.onBeforePopUpClose){
this.onBeforePopUpClose.call(
null, this, this.popUpFltElms[colIndex], colIndex);
}
popUpFltElm.style.display = 'none';
if(this.onAfterPopUpClose){
this.onAfterPopUpClose.call(
null, this, this.popUpFltElms[colIndex], colIndex);
}
}
this.onAfterOpen(this, container, colIndex);
}
/**
* Close popup filter of specified column
* @param {Number} colIndex Column index
*/
close(colIndex) {
let container = this.fltElms[colIndex];
this.onBeforeClose(this, container, colIndex);
container.style.display = NONE;
if (this.activeFilterIdx === colIndex) {
this.activeFilterIdx = -1;
}
removeEvt(root, 'mouseup', (evt) => this.onMouseup(evt));
this.onAfterClose(this, container, colIndex);
}
/**
* Check if popup filter for specified column is open
* @param {Number} colIndex Column index
* @returns {Boolean}
*/
isOpen(colIndex) {
return this.fltElms[colIndex].style.display === 'block';
}
/**
* Close all filters excepted for the specified one if any
* @param {Number} exceptIdx Column index of the filter to not close
*/
closeAll(exceptIdx) {
// Do not close filters only if argument is undefined and close on
// filtering option is disabled
if (isUndef(exceptIdx) && !this.closeOnFiltering) {
return;
}
for (let i = 0; i < this.fltElms.length; i++) {
if (i === exceptIdx) {
closeAll(exceptIdx){
for(var i=0; i<this.popUpFltElms.length; i++){
if(i === exceptIdx){
continue;
}
let fltType = this.tf.getFilterType(i);
let isMultipleFilter =
(fltType === CHECKLIST || fltType === MULTIPLE);
// Always hide all single selection filter types but hide multiple
// selection filter types only if index set
if (!isMultipleFilter || !isUndef(exceptIdx)) {
this.close(i);
var popUpFltElm = this.popUpFltElms[i];
if(popUpFltElm){
popUpFltElm.style.display = 'none';
}
}
}
@ -386,9 +212,9 @@ export class PopupFilter extends Feature {
/**
* Build all the icons representing the pop-up filters
*/
setIconsState() {
for (let i = 0; i < this.fltIcons.length; i++) {
this.changeState(i, false);
buildIcons(){
for(var i=0; i<this.popUpFltImgs.length; i++){
this.buildIcon(i, false);
}
}
@ -397,65 +223,54 @@ export class PopupFilter extends Feature {
* @param {Number} colIndex Column index
* @param {Boolean} active Apply active state
*/
changeState(colIndex, active) {
let icon = this.fltIcons[colIndex];
if (icon) {
icon.src = active ? this.activeIconPath : this.iconPath;
buildIcon(colIndex, active){
if(this.popUpFltImgs[colIndex]){
this.popUpFltImgs[colIndex].src = active ?
this.popUpImgFltActive : this.popUpImgFlt;
}
}
/**
* Remove pop-up filters
*/
destroy() {
if (!this.initialized) {
destroy(){
if(!this.initialized){
return;
}
this.filtersCache = [];
for (let i = 0; i < this.fltElms.length; i++) {
let container = this.fltElms[i],
placeholder = container.parentNode,
icon = this.fltSpans[i],
iconImg = this.fltIcons[i];
if (container) {
removeElm(container);
this.filtersCache[i] = container;
this.popUpFltElmCache = [];
for(var i=0; i<this.popUpFltElms.length; i++){
var popUpFltElm = this.popUpFltElms[i],
popUpFltSpan = this.popUpFltSpans[i],
popUpFltImg = this.popUpFltImgs[i];
if(popUpFltElm){
Dom.remove(popUpFltElm);
this.popUpFltElmCache[i] = popUpFltElm;
}
container = null;
if (placeholder) {
removeElm(placeholder);
popUpFltElm = null;
if(popUpFltSpan){
Dom.remove(popUpFltSpan);
}
placeholder = null;
if (icon) {
removeElm(icon);
popUpFltSpan = null;
if(popUpFltImg){
Dom.remove(popUpFltImg);
}
icon = null;
if (iconImg) {
removeElm(iconImg);
}
iconImg = null;
popUpFltImg = null;
}
this.fltElms = [];
this.fltSpans = [];
this.fltIcons = [];
// TODO: expose an API to handle external filter IDs
this.tf.externalFltIds = [];
this.popUpFltElms = [];
this.popUpFltSpans = [];
this.popUpFltImgs = [];
// unsubscribe to events
this.emitter.off(['before-filtering'], () => this.setIconsState());
this.emitter.off(['after-filtering'], () => this.closeAll());
this.emitter.off(['before-filtering'], ()=> this.buildIcons());
this.emitter.off(['after-filtering'], ()=> this.closeAll());
this.emitter.off(['cell-processed'],
(tf, cellIndex) => this.changeState(cellIndex, true));
this.emitter.off(['filters-row-inserted'], () => this.buildIcons());
(tf, cellIndex)=> this.buildIcon(cellIndex, true));
this.emitter.off(['filters-row-inserted'], ()=> this.tf.headersRow++);
this.emitter.off(['before-filter-init'],
(tf, colIndex) => this.build(colIndex));
(tf, colIndex)=> this.build(colIndex));
this.initialized = false;
}
}
// TODO: remove as soon as feature name is fixed
PopupFilter.meta = {altName: 'popupFilters'};

View file

@ -1,206 +1,147 @@
import {Feature} from '../feature';
import {createElm, createText, elm, removeElm} from '../dom';
import {EMPTY_FN, isNull} from '../types';
import {defaultsStr, defaultsFn} from '../settings';
import {LEFT} from './toolbar';
import {Feature} from './feature';
import Dom from '../dom';
import Types from '../types';
/**
* Rows counter UI component
* @export
* @class RowsCounter
* @extends {Feature}
*/
export class RowsCounter extends Feature {
export class RowsCounter extends Feature{
/**
* Creates an instance of RowsCounter
* @param {TableFilter} tf TableFilter instance
* Rows counter
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, RowsCounter);
constructor(tf){
super(tf, 'rowsCounter');
// TableFilter configuration
let f = this.config.rows_counter || {};
var f = this.config;
/**
* ID of custom container element
* @type {String}
*/
this.targetId = defaultsStr(f.target_id, null);
/**
* Container DOM element
* @type {DOMElement}
* @private
*/
this.container = null;
/**
* Container DOM element for label displaying the total number of rows
* @type {DOMElement}
* @private
*/
this.label = null;
/**
* Text preceding the total number of rows
* @type {String}
*/
this.text = defaultsStr(f.text, 'Rows: ');
/**
* Separator symbol appearing between the first and last visible rows of
* current page when paging is enabled. ie: Rows: 31-40 / 70
* @type {String}
*/
this.fromToTextSeparator = defaultsStr(f.separator, '-');
/**
* Separator symbol appearing between the first and last visible rows of
* current page and the total number of filterable rows when paging is
* enabled. ie: Rows: 31-40 / 70
* @type {String}
*/
this.overText = defaultsStr(f.over_text, ' / ');
/**
* Css class for container element
* @type {String}
*/
this.cssClass = defaultsStr(f.css_class, 'tot');
/**
* Default position in toolbar ('left'|'center'|'right')
* @type {String}
*/
this.toolbarPosition = defaultsStr(f.toolbar_position, LEFT);
/**
* Callback fired before the counter is refreshed
* @type {Function}
*/
this.onBeforeRefreshCounter = defaultsFn(f.on_before_refresh_counter,
EMPTY_FN);
/**
* Callback fired after the counter is refreshed
* @type {Function}
*/
this.onAfterRefreshCounter = defaultsFn(f.on_after_refresh_counter,
EMPTY_FN);
//id of custom container element
this.rowsCounterTgtId = f.rows_counter_target_id || null;
//element containing tot nb rows
this.rowsCounterDiv = null;
//element containing tot nb rows label
this.rowsCounterSpan = null;
//defines rows counter text
this.rowsCounterText = f.rows_counter_text || 'Rows: ';
this.fromToTextSeparator = f.from_to_text_separator || '-';
this.overText = f.over_text || ' / ';
//defines css class rows counter
this.totRowsCssClass = f.tot_rows_css_class || 'tot';
//rows counter div
this.prfxCounter = 'counter_';
//nb displayed rows label
this.prfxTotRows = 'totrows_span_';
//label preceding nb rows label
this.prfxTotRowsTxt = 'totRowsTextSpan_';
//callback raised before counter is refreshed
this.onBeforeRefreshCounter = Types.isFn(f.on_before_refresh_counter) ?
f.on_before_refresh_counter : null;
//callback raised after counter is refreshed
this.onAfterRefreshCounter = Types.isFn(f.on_after_refresh_counter) ?
f.on_after_refresh_counter : null;
}
/**
* Initializes RowsCounter instance
*/
init() {
if (this.initialized) {
init(){
if(this.initialized){
return;
}
this.emitter.emit('initializing-feature', this, !isNull(this.targetId));
let tf = this.tf;
var tf = this.tf;
//rows counter container
let countDiv = createElm('div');
countDiv.className = this.cssClass;
var countDiv = Dom.create('div', ['id', this.prfxCounter+tf.id]);
countDiv.className = this.totRowsCssClass;
//rows counter label
let countSpan = createElm('span');
let countText = createElm('span');
countText.appendChild(createText(this.text));
var countSpan = Dom.create('span', ['id', this.prfxTotRows+tf.id]);
var countText = Dom.create('span', ['id', this.prfxTotRowsTxt+tf.id]);
countText.appendChild(Dom.text(this.rowsCounterText));
// counter is added to defined element
let targetEl = !this.targetId ?
tf.feature('toolbar').container(this.toolbarPosition) :
elm(this.targetId);
if(!this.rowsCounterTgtId){
tf.setToolbar();
}
var targetEl = !this.rowsCounterTgtId ?
tf.lDiv : Dom.id( this.rowsCounterTgtId );
//default container: 'lDiv'
if (!this.targetId) {
if(!this.rowsCounterTgtId){
countDiv.appendChild(countText);
countDiv.appendChild(countSpan);
targetEl.appendChild(countDiv);
} else {
}
else{
//custom container, no need to append statusDiv
targetEl.appendChild(countText);
targetEl.appendChild(countSpan);
}
this.container = countDiv;
this.label = countSpan;
this.rowsCounterDiv = countDiv;
this.rowsCounterSpan = countSpan;
// subscribe to events
this.emitter.on(['after-filtering', 'grouped-by-page'],
() => this.refresh(tf.getValidRowsNb()));
this.emitter.on(['rows-changed'], () => this.refresh());
()=> this.refresh(tf.nbVisibleRows));
this.emitter.on(['rows-changed'], ()=> this.refresh());
/** @inherited */
this.initialized = true;
this.refresh();
this.emitter.emit('feature-initialized', this);
}
/**
* Refreshes the rows counter
* @param {Number} p Optional parameter the total number of rows to display
*/
refresh(p) {
if (!this.initialized || !this.isEnabled()) {
refresh(p){
if(!this.initialized || !this.isEnabled()){
return;
}
let tf = this.tf;
var tf = this.tf;
this.onBeforeRefreshCounter(tf, this.label);
if(this.onBeforeRefreshCounter){
this.onBeforeRefreshCounter.call(null, tf, this.rowsCounterSpan);
}
let totTxt;
if (!tf.paging) {
if (p && p !== '') {
var totTxt;
if(!tf.paging){
if(p && p !== ''){
totTxt = p;
} else {
totTxt = tf.getFilterableRowsNb() - tf.nbHiddenRows;
} else{
totTxt = tf.nbFilterableRows - tf.nbHiddenRows;
}
} else {
let paging = tf.feature('paging');
if (paging) {
let nbValidRows = tf.getValidRowsNb();
var paging = tf.feature('paging');
if(paging){
//paging start row
let pagingStartRow = parseInt(paging.startPagingRow, 10) +
((nbValidRows > 0) ? 1 : 0);
let pagingEndRow =
(pagingStartRow + paging.pageLength) - 1 <=
nbValidRows ?
pagingStartRow + paging.pageLength - 1 :
nbValidRows;
totTxt = pagingStartRow + this.fromToTextSeparator +
pagingEndRow + this.overText + nbValidRows;
var paging_start_row = parseInt(paging.startPagingRow, 10) +
((tf.nbVisibleRows>0) ? 1 : 0);
var paging_end_row = (paging_start_row+paging.pagingLength)-1 <=
tf.nbVisibleRows ?
paging_start_row+paging.pagingLength-1 :
tf.nbVisibleRows;
totTxt = paging_start_row + this.fromToTextSeparator +
paging_end_row + this.overText + tf.nbVisibleRows;
}
}
this.label.innerHTML = totTxt;
this.onAfterRefreshCounter(tf, this.label, totTxt);
this.rowsCounterSpan.innerHTML = totTxt;
if(this.onAfterRefreshCounter){
this.onAfterRefreshCounter.call(
null, tf, this.rowsCounterSpan, totTxt);
}
}
/**
* Remove feature
*/
destroy() {
if (!this.initialized) {
destroy(){
if(!this.initialized){
return;
}
if (!this.targetId && this.container) {
removeElm(this.container);
if(!this.rowsCounterTgtId && this.rowsCounterDiv){
Dom.remove(this.rowsCounterDiv);
} else {
elm(this.targetId).innerHTML = '';
Dom.id(this.rowsCounterTgtId).innerHTML = '';
}
this.label = null;
this.container = null;
this.rowsCounterSpan = null;
this.rowsCounterDiv = null;
// unsubscribe to events
this.emitter.off(['after-filtering', 'grouped-by-page'],
() => this.refresh(tf.getValidRowsNb()));
this.emitter.off(['rows-changed'], () => this.refresh());
()=> this.refresh(tf.nbVisibleRows));
this.emitter.off(['rows-changed'], ()=> this.refresh());
this.initialized = false;
}

View file

@ -1,545 +0,0 @@
import {Feature} from '../feature';
import {Hash} from './hash';
import {Storage} from './storage';
import {isEmpty} from '../string';
import {isArray, isNull, isString, isUndef} from '../types';
import {defaultsBool, defaultsNb} from '../settings';
/**
* Features state object persistable with localStorage, cookie or URL hash
*
* @export
* @class State
* @extends {Feature}
*/
export class State extends Feature {
/**
* Creates an instance of State
* @param {TableFilter} tf TableFilter instance
*/
constructor(tf) {
super(tf, State);
let cfg = this.config.state || {};
/**
* Determines whether state is persisted with URL hash
* @type {Boolean}
*/
this.enableHash = cfg === true ||
(isArray(cfg.types) && cfg.types.indexOf('hash') !== -1);
/**
* Determines whether state is persisted with localStorage
* @type {Boolean}
*/
this.enableLocalStorage = isArray(cfg.types) &&
cfg.types.indexOf('local_storage') !== -1;
/**
* Determines whether state is persisted with localStorage
* @type {Boolean}
*/
this.enableCookie = isArray(cfg.types) &&
cfg.types.indexOf('cookie') !== -1;
/**
* Persist filters values, enabled by default
* @type {Boolean}
*/
this.persistFilters = defaultsBool(cfg.filters, true);
/**
* Persist current page number when paging is enabled
* @type {Boolean}
*/
this.persistPageNumber = Boolean(cfg.page_number);
/**
* Persist page length when paging is enabled
* @type {Boolean}
*/
this.persistPageLength = Boolean(cfg.page_length);
/**
* Persist column sorting
* @type {Boolean}
*/
this.persistSort = Boolean(cfg.sort);
/**
* Persist columns visibility
* @type {Boolean}
*/
this.persistColsVisibility = Boolean(cfg.columns_visibility);
/**
* Persist filters row visibility
* @type {Boolean}
*/
this.persistFiltersVisibility = Boolean(cfg.filters_visibility);
/**
* Cookie duration in hours
* @type {Boolean}
*/
this.cookieDuration = defaultsNb(parseInt(cfg.cookie_duration, 10),
87600);
/**
* Enable Storage if localStorage or cookie is required
* @type {Boolean}
* @private
*/
this.enableStorage = this.enableLocalStorage || this.enableCookie;
/**
* Storage instance if storage is required
* @type {Storage}
* @private
*/
this.storage = null;
/**
* Hash instance if URL hash is required
* @type {Boolean}
* @private
*/
this.hash = null;
/**
* Current page number
* @type {Number}
* @private
*/
this.pageNb = null;
/**
* Current page length
* @type {Number}
* @private
*/
this.pageLength = null;
/**
* Current column sorting
* @type {Object}
* @private
*/
this.sort = null;
/**
* Current hidden columns
* @type {Object}
* @private
*/
this.hiddenCols = null;
/**
* Filters row visibility
* @type {Boolean}
* @private
*/
this.filtersVisibility = null;
/**
* State object
* @type {Object}
* @private
*/
this.state = {};
/**
* Prefix for column ID
* @type {String}
* @private
*/
this.prfxCol = 'col_';
/**
* Prefix for page number ID
* @type {String}
* @private
*/
this.pageNbKey = 'page';
/**
* Prefix for page length ID
* @type {String}
* @private
*/
this.pageLengthKey = 'page_length';
/**
* Prefix for filters visibility ID
* @type {String}
* @private
*/
this.filtersVisKey = 'filters_visibility';
}
/**
* Initializes State instance
*/
init() {
if (this.initialized) {
return;
}
this.emitter.on(['after-filtering'], () => this.update());
this.emitter.on(['after-page-change', 'after-clearing-filters'],
(tf, pageNb) => this.updatePage(pageNb));
this.emitter.on(['after-page-length-change'],
(tf, pageLength) => this.updatePageLength(pageLength));
this.emitter.on(['column-sorted'],
(tf, index, descending) => this.updateSort(index, descending));
this.emitter.on(['sort-initialized'], () => this._syncSort());
this.emitter.on(['columns-visibility-initialized'],
() => this._syncColsVisibility());
this.emitter.on(['column-shown', 'column-hidden'], (tf, feature,
colIndex, hiddenCols) => this.updateColsVisibility(hiddenCols));
this.emitter.on(['filters-visibility-initialized'],
() => this._syncFiltersVisibility());
this.emitter.on(['filters-toggled'],
(tf, extension, visible) => this.updateFiltersVisibility(visible));
if (this.enableHash) {
this.hash = new Hash(this);
this.hash.init();
}
if (this.enableStorage) {
this.storage = new Storage(this);
this.storage.init();
}
/** @inherited */
this.initialized = true;
}
/**
* Update state object based on current features state
*/
update() {
if (!this.isEnabled()) {
return;
}
let state = this.state;
let tf = this.tf;
if (this.persistFilters) {
let filterValues = tf.getFiltersValue();
filterValues.forEach((val, idx) => {
let key = `${this.prfxCol}${idx}`;
if (isString(val) && isEmpty(val)) {
if (state.hasOwnProperty(key)) {
state[key].flt = undefined;
}
} else {
state[key] = state[key] || {};
state[key].flt = val;
}
});
}
if (this.persistPageNumber) {
if (isNull(this.pageNb)) {
state[this.pageNbKey] = undefined;
} else {
state[this.pageNbKey] = this.pageNb;
}
}
if (this.persistPageLength) {
if (isNull(this.pageLength)) {
state[this.pageLengthKey] = undefined;
} else {
state[this.pageLengthKey] = this.pageLength;
}
}
if (this.persistSort) {
if (!isNull(this.sort)) {
// Remove previuosly sorted column
Object.keys(state).forEach((key) => {
if (key.indexOf(this.prfxCol) !== -1 && state[key]) {
state[key].sort = undefined;
}
});
let key = `${this.prfxCol}${this.sort.column}`;
state[key] = state[key] || {};
state[key].sort = { descending: this.sort.descending };
}
}
if (this.persistColsVisibility) {
if (!isNull(this.hiddenCols)) {
// Clear previuosly hidden columns
Object.keys(state).forEach((key) => {
if (key.indexOf(this.prfxCol) !== -1 && state[key]) {
state[key].hidden = undefined;
}
});
this.hiddenCols.forEach((colIdx) => {
let key = `${this.prfxCol}${colIdx}`;
state[key] = state[key] || {};
state[key].hidden = true;
});
}
}
if (this.persistFiltersVisibility) {
if (isNull(this.filtersVisibility)) {
state[this.filtersVisKey] = undefined;
} else {
state[this.filtersVisKey] = this.filtersVisibility;
}
}
this.emitter.emit('state-changed', tf, state);
}
/**
* Refresh page number field on page number changes
*
* @param {Number} pageNb Current page number
*/
updatePage(pageNb) {
this.pageNb = pageNb;
this.update();
}
/**
* Refresh page length field on page length changes
*
* @param {Number} pageLength Current page length value
*/
updatePageLength(pageLength) {
this.pageLength = pageLength;
this.update();
}
/**
* Refresh column sorting information on sort changes
*
* @param index {Number} Column index
* @param {Boolean} descending Descending manner
*/
updateSort(index, descending) {
this.sort = {
column: index,
descending: descending
};
this.update();
}
/**
* Refresh hidden columns information on columns visibility changes
*
* @param {Array} hiddenCols Columns indexes
*/
updateColsVisibility(hiddenCols) {
this.hiddenCols = hiddenCols;
this.update();
}
/**
* Refresh filters visibility on filters visibility change
*
* @param {Boolean} visible Visibility flad
*/
updateFiltersVisibility(visible) {
this.filtersVisibility = visible;
this.update();
}
/**
* Override state field
*
* @param state State object
*/
override(state) {
this.state = state;
this.emitter.emit('state-changed', this.tf, state);
}
/**
* Sync stored features state
*/
sync() {
let state = this.state;
let tf = this.tf;
this._syncFilters();
if (this.persistPageNumber) {
let pageNumber = state[this.pageNbKey];
this.emitter.emit('change-page', tf, pageNumber);
}
if (this.persistPageLength) {
let pageLength = state[this.pageLengthKey];
this.emitter.emit('change-page-results', tf, pageLength);
}
this._syncSort();
this._syncColsVisibility();
this._syncFiltersVisibility();
}
/**
* Override current state with passed one and sync features
*
* @param {Object} state State object
*/
overrideAndSync(state) {
// To prevent state to react to features changes, state is temporarily
// disabled
this.disable();
// State is overriden with passed state object
this.override(state);
// New hash state is applied to features
this.sync();
// State is re-enabled
this.enable();
}
/**
* Sync filters with stored values and filter table
*
* @private
*/
_syncFilters() {
if (!this.persistFilters) {
return;
}
let state = this.state;
let tf = this.tf;
// clear all filters
// TODO: use tf.clearFilters() once it allows to not filter the table
tf.eachCol((colIdx) => tf.setFilterValue(colIdx, ''));
Object.keys(state).forEach((key) => {
if (key.indexOf(this.prfxCol) !== -1) {
let colIdx = parseInt(key.replace(this.prfxCol, ''), 10);
let val = state[key].flt;
tf.setFilterValue(colIdx, val);
}
});
tf.filter();
}
/**
* Sync sorted column with stored sorting information and sort table
*
* @private
*/
_syncSort() {
if (!this.persistSort) {
return;
}
let state = this.state;
let tf = this.tf;
Object.keys(state).forEach((key) => {
if (key.indexOf(this.prfxCol) !== -1) {
let colIdx = parseInt(key.replace(this.prfxCol, ''), 10);
if (!isUndef(state[key].sort)) {
let sort = state[key].sort;
this.emitter.emit('sort', tf, colIdx, sort.descending);
}
}
});
}
/**
* Sync hidden columns with stored information
*
* @private
*/
_syncColsVisibility() {
if (!this.persistColsVisibility) {
return;
}
let state = this.state;
let tf = this.tf;
let hiddenCols = [];
Object.keys(state).forEach((key) => {
if (key.indexOf(this.prfxCol) !== -1) {
let colIdx = parseInt(key.replace(this.prfxCol, ''), 10);
if (!isUndef(state[key].hidden)) {
hiddenCols.push(colIdx);
}
}
});
hiddenCols.forEach((colIdx) => {
this.emitter.emit('hide-column', tf, colIdx);
});
}
/**
* Sync filters visibility with stored information
*
* @private
*/
_syncFiltersVisibility() {
if (!this.persistFiltersVisibility) {
return;
}
let state = this.state;
let tf = this.tf;
let filtersVisibility = state[this.filtersVisKey];
this.filtersVisibility = filtersVisibility;
this.emitter.emit('show-filters', tf, filtersVisibility);
}
/**
* Destroy State instance
*/
destroy() {
if (!this.initialized) {
return;
}
this.state = {};
this.emitter.off(['after-filtering'], () => this.update());
this.emitter.off(['after-page-change', 'after-clearing-filters'],
(tf, pageNb) => this.updatePage(pageNb));
this.emitter.off(['after-page-length-change'],
(tf, index) => this.updatePageLength(index));
this.emitter.off(['column-sorted'],
(tf, index, descending) => this.updateSort(index, descending));
this.emitter.off(['sort-initialized'], () => this._syncSort());
this.emitter.off(['columns-visibility-initialized'],
() => this._syncColsVisibility());
this.emitter.off(['column-shown', 'column-hidden'], (tf, feature,
colIndex, hiddenCols) => this.updateColsVisibility(hiddenCols));
this.emitter.off(['filters-visibility-initialized'],
() => this._syncFiltersVisibility());
this.emitter.off(['filters-toggled'],
(tf, extension, visible) => this.updateFiltersVisibility(visible));
if (this.enableHash) {
this.hash.destroy();
this.hash = null;
}
if (this.enableStorage) {
this.storage.destroy();
this.storage = null;
}
this.initialized = false;
}
}

View file

@ -1,207 +1,106 @@
import {Feature} from '../feature';
import {root} from '../root';
import {createElm, createText, elm, removeElm} from '../dom';
import {EMPTY_FN, isNull} from '../types';
import {defaultsStr, defaultsFn} from '../settings';
import {LEFT} from './toolbar';
import {Feature} from './feature';
import Dom from '../dom';
import Types from '../types';
const EVENTS = [
'after-filtering',
'after-populating-filter',
'after-page-change',
'after-clearing-filters',
'after-page-length-change',
'after-reset-page',
'after-reset-page-length',
'after-loading-extensions',
'after-loading-themes'
];
let global = window;
/**
* Status bar UI component
* @export
* @class StatusBar
* @extends {Feature}
*/
export class StatusBar extends Feature {
export class StatusBar extends Feature{
/**
* Creates an instance of StatusBar
* @param {TableFilter} tf TableFilter instance
* Status bar UI component
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, StatusBar);
constructor(tf){
super(tf, 'statusBar');
// Configuration object
let f = this.config.status_bar || {};
let f = this.config;
/**
* ID of custom container element
* @type {String}
*/
this.targetId = defaultsStr(f.target_id, null);
//id of custom container element
this.statusBarTgtId = f.status_bar_target_id || null;
//element containing status bar label
this.statusBarDiv = null;
//status bar
this.statusBarSpan = null;
//status bar label
this.statusBarSpanText = null;
//defines status bar text
this.statusBarText = f.status_bar_text || '';
//defines css class status bar
this.statusBarCssClass = f.status_bar_css_class || 'status';
//delay for status bar clearing
this.statusBarCloseDelay = 250;
/**
* Container DOM element
* @type {DOMElement}
* @private
*/
this.container = null;
//calls function before message is displayed
this.onBeforeShowMsg = Types.isFn(f.on_before_show_msg) ?
f.on_before_show_msg : null;
//calls function after message is displayed
this.onAfterShowMsg = Types.isFn(f.on_after_show_msg) ?
f.on_after_show_msg : null;
/**
* Message container DOM element
* @type {DOMElement}
* @private
*/
this.msgContainer = null;
//status messages
this.msgFilter = f.msg_filter || 'Filtering data...';
//populating drop-downs
this.msgPopulate = f.msg_populate || 'Populating filter...';
//populating drop-downs
this.msgPopulateCheckList = f.msg_populate_checklist ||
'Populating list...';
//changing paging page
this.msgChangePage = f.msg_change_page || 'Collecting paging data...';
//clearing filters
this.msgClear = f.msg_clear || 'Clearing filters...';
//changing nb results/page
this.msgChangeResults = f.msg_change_results ||
'Changing results per page...';
//re-setting page
this.msgResetPage = f.msg_reset_page || 'Re-setting page...';
//re-setting page length
this.msgResetPageLength = f.msg_reset_page_length ||
'Re-setting page length...';
//table sorting
this.msgSort = f.msg_sort || 'Sorting data...';
//extensions loading
this.msgLoadExtensions = f.msg_load_extensions ||
'Loading extensions...';
//themes loading
this.msgLoadThemes = f.msg_load_themes || 'Loading theme(s)...';
/**
* Label container DOM element
* @type {DOMElement}
* @private
*/
this.labelContainer = null;
/**
* Text preceding status message
* @type {String}
*/
this.text = defaultsStr(f.text, '');
/**
* Css class for container element
* @type {String}
*/
this.cssClass = defaultsStr(f.css_class, 'status');
/**
* Message visibility duration in milliseconds
* @type {Number}
* @private
*/
this.delay = 250;
/**
* Callback fired before the message is displayed
* @type {Function}
*/
this.onBeforeShowMsg = defaultsFn(f.on_before_show_msg, EMPTY_FN);
/**
* Callback fired after the message is displayed
* @type {Function}
*/
this.onAfterShowMsg = defaultsFn(f.on_after_show_msg, EMPTY_FN);
/**
* Message appearing upon filtering
* @type {String}
*/
this.msgFilter = defaultsStr(f.msg_filter, 'Filtering data...');
/**
* Message appearing when a drop-down filter is populated
* @type {String}
*/
this.msgPopulate = defaultsStr(f.msg_populate, 'Populating filter...');
/**
* Message appearing when a checklist filter is populated
* @type {String}
*/
this.msgPopulateCheckList = defaultsStr(f.msg_populate_checklist,
'Populating list...');
/**
* Message appearing when a pagination page is changed
* @type {String}
*/
this.msgChangePage = defaultsStr(f.msg_change_page,
'Collecting paging data...');
/**
* Message appearing when filters are cleared
* @type {String}
*/
this.msgClear = defaultsStr(f.msg_clear, 'Clearing filters...');
/**
* Message appearing when the page length is changed
* @type {String}
*/
this.msgChangeResults = defaultsStr(f.msg_change_results,
'Changing results per page...');
/**
* Message appearing when the page is re-set
* @type {String}
*/
this.msgResetPage = defaultsStr(f.msg_reset_page, 'Re-setting page...');
/**
* Message appearing when the page length is re-set
* @type {String}
*/
this.msgResetPageLength = defaultsStr(f.msg_reset_page_length,
'Re-setting page length...');
/**
* Message appearing upon column sorting
* @type {String}
*/
this.msgSort = defaultsStr(f.msg_sort, 'Sorting data...');
/**
* Message appearing when extensions are loading
* @type {String}
*/
this.msgLoadExtensions = defaultsStr(f.msg_load_extensions,
'Loading extensions...');
/**
* Message appearing when themes are loading
* @type {String}
*/
this.msgLoadThemes = defaultsStr(f.msg_load_themes,
'Loading theme(s)...');
/**
* Default position in toolbar ('left'|'center'|'right')
* @type {String}
*/
this.toolbarPosition = defaultsStr(f.toolbar_position, LEFT);
// status bar div
this.prfxStatus = 'status_';
// status bar label
this.prfxStatusSpan = 'statusSpan_';
// text preceding status bar label
this.prfxStatusTxt = 'statusText_';
}
/**
* Initializes StatusBar instance
*/
init() {
if (this.initialized) {
init(){
if(this.initialized){
return;
}
let tf = this.tf;
let emitter = this.emitter;
emitter.emit('initializing-feature', this, !isNull(this.targetId));
//status bar container
let statusDiv = Dom.create('div', ['id', this.prfxStatus+tf.id]);
statusDiv.className = this.statusBarCssClass;
// status bar container
let statusDiv = createElm('div');
statusDiv.className = this.cssClass;
// status bar label
let statusSpan = createElm('span');
// preceding text
let statusSpanText = createElm('span');
statusSpanText.appendChild(createText(this.text));
//status bar label
let statusSpan = Dom.create('span', ['id', this.prfxStatusSpan+tf.id]);
//preceding text
let statusSpanText = Dom.create('span',
['id', this.prfxStatusTxt+tf.id]);
statusSpanText.appendChild(Dom.text(this.statusBarText));
// target element container
let targetEl = (!this.targetId) ?
tf.feature('toolbar').container(this.toolbarPosition) :
elm(this.targetId);
if(!this.statusBarTgtId){
tf.setToolbar();
}
let targetEl = (!this.statusBarTgtId) ?
tf.lDiv : Dom.id(this.statusBarTgtId);
// default container
if (!this.targetId) {
//default container: 'lDiv'
if(!this.statusBarTgtId){
statusDiv.appendChild(statusSpanText);
statusDiv.appendChild(statusSpan);
targetEl.appendChild(statusDiv);
@ -211,98 +110,115 @@ export class StatusBar extends Feature {
targetEl.appendChild(statusSpan);
}
this.container = statusDiv;
this.msgContainer = statusSpan;
this.labelContainer = statusSpanText;
this.statusBarDiv = statusDiv;
this.statusBarSpan = statusSpan;
this.statusBarSpanText = statusSpanText;
// subscribe to events
emitter.on(['before-filtering'], () => this.message(this.msgFilter));
// Subscribe to events
emitter.on(['before-filtering'], ()=> this.message(this.msgFilter));
emitter.on(['before-populating-filter'],
() => this.message(this.msgPopulate));
emitter.on(['before-page-change'],
() => this.message(this.msgChangePage));
emitter.on(['before-clearing-filters'], () =>
()=> this.message(this.msgPopulate));
emitter.on(['before-changing-page'],
()=> this.message(this.msgChangePage));
emitter.on(['before-clearing-filters'], ()=>
this.message(this.msgClear));
emitter.on(['before-page-length-change'],
() => this.message(this.msgChangeResults));
emitter.on(['before-reset-page'],
() => this.message(this.msgResetPage));
emitter.on(['before-changing-results-per-page'],
()=> this.message(this.msgChangeResults));
emitter.on(['before-reset-page'], ()=> this.message(this.msgResetPage));
emitter.on(['before-reset-page-length'],
() => this.message(this.msgResetPageLength));
()=> this.message(this.msgResetPageLength));
emitter.on(['before-loading-extensions'],
() => this.message(this.msgLoadExtensions));
()=> this.message(this.msgLoadExtensions));
emitter.on(['before-loading-themes'],
() => this.message(this.msgLoadThemes));
()=> this.message(this.msgLoadThemes));
emitter.on(EVENTS, () => this.message(''));
emitter.on([
'after-filtering',
'after-populating-filter',
'after-changing-page',
'after-clearing-filters',
'after-changing-results-per-page',
'after-reset-page',
'after-reset-page-length',
'after-loading-extensions',
'after-loading-themes'
],
()=> this.message('')
);
/** @inherited */
this.initialized = true;
emitter.emit('feature-initialized', this);
}
/**
* Display status message
* @param {String} [t=''] Message to be displayed
*/
message(t = '') {
if (!this.isEnabled()) {
message(t=''){
if(!this.isEnabled()){
return;
}
this.onBeforeShowMsg(this.tf, t);
if(this.onBeforeShowMsg){
this.onBeforeShowMsg.call(null, this.tf, t);
}
let d = t === '' ? this.delay : 1;
root.setTimeout(() => {
if (!this.initialized) {
let d = t==='' ? this.statusBarCloseDelay : 1;
global.setTimeout(() => {
if(!this.initialized){
return;
}
this.msgContainer.innerHTML = t;
this.onAfterShowMsg(this.tf, t);
this.statusBarSpan.innerHTML = t;
if(this.onAfterShowMsg){
this.onAfterShowMsg.call(null, this.tf, t);
}
}, d);
}
/**
* Destroy StatusBar instance
*/
destroy() {
if (!this.initialized) {
destroy(){
if(!this.initialized){
return;
}
let emitter = this.emitter;
this.container.innerHTML = '';
if (!this.targetId) {
removeElm(this.container);
this.statusBarDiv.innerHTML = '';
if(!this.statusBarTgtId){
Dom.remove(this.statusBarDiv);
}
this.labelContainer = null;
this.msgContainer = null;
this.container = null;
this.statusBarSpan = null;
this.statusBarSpanText = null;
this.statusBarDiv = null;
// Unsubscribe to events
emitter.off(['before-filtering'], () => this.message(this.msgFilter));
emitter.off(['before-filtering'], ()=> this.message(this.msgFilter));
emitter.off(['before-populating-filter'],
() => this.message(this.msgPopulate));
emitter.off(['before-page-change'],
() => this.message(this.msgChangePage));
()=> this.message(this.msgPopulate));
emitter.off(['before-changing-page'],
()=> this.message(this.msgChangePage));
emitter.off(['before-clearing-filters'],
() => this.message(this.msgClear));
emitter.off(['before-page-length-change'],
() => this.message(this.msgChangeResults));
emitter.off(['before-reset-page'], () =>
()=> this.message(this.msgClear));
emitter.off(['before-changing-results-per-page'],
()=> this.message(this.msgChangeResults));
emitter.off(['before-reset-page'], ()=>
this.message(this.msgResetPage));
emitter.off(['before-reset-page-length'],
() => this.message(this.msgResetPageLength));
()=> this.message(this.msgResetPageLength));
emitter.off(['before-loading-extensions'],
() => this.message(this.msgLoadExtensions));
()=> this.message(this.msgLoadExtensions));
emitter.off(['before-loading-themes'],
() => this.message(this.msgLoadThemes));
()=> this.message(this.msgLoadThemes));
emitter.off(EVENTS, () => this.message(''));
emitter.off([
'after-filtering',
'after-populating-filter',
'after-changing-page',
'after-clearing-filters',
'after-changing-results-per-page',
'after-reset-page',
'after-reset-page-length',
'after-loading-extensions',
'after-loading-themes'
],
()=> this.message('')
);
this.initialized = false;
}
}

View file

@ -1,161 +0,0 @@
import Cookie from '../cookie';
import {root} from '../root';
const JSON = root.JSON;
const localStorage = root.localStorage;
const location = root.location;
/**
* Checks if browser has Storage feature
*/
export const hasStorage = () => {
return 'Storage' in root;
};
/**
* Stores the features state in browser's local storage or cookie
*
* @export
* @class Storage
*/
export class Storage {
/**
* Creates an instance of Storage
*
* @param {State} state Instance of State
*/
constructor(state) {
/**
* State object
* @type {State}
* @private
*/
this.state = state;
/**
* TableFilter object
* @type {TableFilter}
* @private
*/
this.tf = state.tf;
/**
* Persist with local storage
* @type {Boolean}
* @private
*/
this.enableLocalStorage = state.enableLocalStorage && hasStorage();
/**
* Persist with cookie
* @type {Boolean}
* @private
*/
this.enableCookie = state.enableCookie && !this.enableLocalStorage;
/**
* Emitter object
* @type {Emitter}
* @private
*/
this.emitter = state.emitter;
/**
* Cookie duration in hours from state object
* @type {Number}
* @private
*/
this.duration = state.cookieDuration;
}
/**
* Initializes the Storage object
*/
init() {
this.emitter.on(['state-changed'], (tf, state) => this.save(state));
this.emitter.on(['initialized'], () => this.sync());
}
/**
* Persists the features state on state changes
*
* @param {State} state Instance of State
*/
save(state) {
if (this.enableLocalStorage) {
localStorage[this.getKey()] = JSON.stringify(state);
} else {
Cookie.write(this.getKey(), JSON.stringify(state), this.duration);
}
}
/**
* Turns stored string into a State JSON object
*
* @returns {Object} JSON object
*/
retrieve() {
let state = null;
if (this.enableLocalStorage) {
state = localStorage[this.getKey()];
} else {
state = Cookie.read(this.getKey());
}
if (!state) {
return null;
}
return JSON.parse(state);
}
/**
* Removes persisted state from storage
*/
remove() {
if (this.enableLocalStorage) {
localStorage.removeItem(this.getKey());
} else {
Cookie.remove(this.getKey());
}
}
/**
* Applies persisted state to features
*/
sync() {
let state = this.retrieve();
if (!state) {
return;
}
// override current state with persisted one and sync features
this.state.overrideAndSync(state);
}
/**
* Returns the storage key
*
* @returns {String} Key
*/
getKey() {
return JSON.stringify({
key: `${this.tf.prfxTf}_${this.tf.id}`,
path: location.pathname
});
}
/**
* Release Storage event subscriptions and clear fields
*/
destroy() {
this.emitter.off(['state-changed'], (tf, state) => this.save(state));
this.emitter.off(['initialized'], () => this.sync());
this.remove();
this.state = null;
this.emitter = null;
}
}

146
src/modules/store.js Normal file
View file

@ -0,0 +1,146 @@
import Cookie from '../cookie';
import Types from '../types';
export class Store{
/**
* Store, persistence manager
* @param {Object} tf TableFilter instance
*
* TODO: use localStorage and fallback to cookie persistence
*/
constructor(tf){
let f = tf.config();
//cookie storing filter values
this.fltsValuesCookie = tf.prfxCookieFltsValues + tf.id;
//cookie storing page nb
this.pgNbCookie = tf.prfxCookiePageNb + tf.id;
//cookie storing page length
this.pgLenCookie = tf.prfxCookiePageLen + tf.id;
this.duration = !isNaN(f.set_cookie_duration) ?
parseInt(f.set_cookie_duration, 10) : 100000;
this.tf = tf;
this.emitter = tf.emitter;
}
init(){
this.emitter.on(['after-filtering'], ()=> this.saveFilterValues());
this.emitter.on(['after-clearing-filters'], ()=> this.clearCookies());
this.emitter.on(['after-changing-page'],
(tf, index)=> this.savePageNb(index));
this.emitter.on(['after-changing-results-per-page'],
(tf, index)=> this.savePageLength(index));
}
/**
* Store filters' values in cookie
*/
saveFilterValues(){
let tf = this.tf;
let fltValues = [];
if(!tf.rememberGridValues){
return;
}
//store filters' values
for(let i=0; i<tf.fltIds.length; i++){
let value = tf.getFilterValue(i);
//convert array to a || separated values
if(Types.isArray(value)){
let rgx = new RegExp(tf.separator, 'g');
value = value.toString()
.replace(rgx, ' ' + tf.orOperator + ' ');
}
if (value === ''){
value = ' ';
}
fltValues.push(value);
}
//write cookie
Cookie.write(
this.fltsValuesCookie,
fltValues.join(tf.separator),
this.duration
);
}
/**
* Retrieve filters' values from cookie
* @return {Array}
*/
getFilterValues(){
let flts = Cookie.read(this.fltsValuesCookie);
let rgx = new RegExp(this.tf.separator, 'g');
// filters' values array
return flts.split(rgx);
}
/**
* Store page number in cookie
* @param {Number} index page index to persist
*/
savePageNb(index){
if(!this.tf.rememberPageNb){
return;
}
Cookie.write(
this.pgNbCookie,
(index+1),
this.duration
);
}
/**
* Retrieve page number from cookie
* @return {String}
*/
getPageNb(){
return Cookie.read(this.pgNbCookie);
}
/**
* Store page length in cookie
* @param {Number} index page length index to persist
*/
savePageLength(index){
if(!this.tf.rememberPageLen){
return;
}
Cookie.write(
this.pgLenCookie,
index,
this.duration
);
}
/**
* Retrieve page length from cookie
* @return {String}
*/
getPageLength(){
return Cookie.read(this.pgLenCookie);
}
/**
* Remove all cookies
*/
clearCookies(){
Cookie.remove(this.fltsValuesCookie);
Cookie.remove(this.pgLenCookie);
Cookie.remove(this.pgNbCookie);
}
destroy(){
this.emitter.off(['after-filtering'], ()=> this.saveFilterValues());
this.emitter.off(['after-clearing-filters'], ()=> this.clearCookies());
this.emitter.off(['after-changing-page'],
(tf, index)=> this.savePageNb(index));
this.emitter.off(['after-changing-results-per-page'],
(tf, index)=> this.savePageLength(index));
}
}

View file

@ -1,224 +0,0 @@
import {Feature} from '../feature';
import {createElm, removeElm, elm, tag} from '../dom';
import {defaultsStr} from '../settings';
import {isUndef} from '../types';
const EVENTS = [
'initializing-feature',
'initializing-extension'
];
/** Left position in toolbar */
export const LEFT = 'left';
/** Right position in toolbar */
export const RIGHT = 'right';
/** Center position in toolbar */
export const CENTER = 'center';
/**
* Toolbar UI component
* @export
* @class Toolbar
* @extends {Feature}
*/
export class Toolbar extends Feature {
/**
* Create an instance of Toolbar
* @param {TableFilter} tf TableFilter instance
* @memberof Toolbar
*/
constructor(tf) {
super(tf, Toolbar);
// Configuration object
let f = this.config.toolbar || {};
/**
* Css class for toolbar's container DOM element
* @type {String}
*/
this.contCssClass = defaultsStr(f.container_css_class, 'inf');
/**
* Css class for left-side inner container DOM element
* @type {String}
*/
this.lContCssClass = defaultsStr(f.left_cont_css_class, 'ldiv');
/**
* Css class for right-side inner container DOM element
* @type {String}
*/
this.rContCssClass = defaultsStr(f.right_cont_css_class, 'rdiv');
/**
* Css class for middle inner container DOM element
* @type {String}
*/
this.cContCssClass = defaultsStr(f.center_cont_css_class, 'mdiv');
/**
* Toolbar's custom container ID
* @type {String}
*/
this.tgtId = defaultsStr(f.target_id, null);
/**
* Toolbar's container DOM element
* @type {DOMElement}
* @private
*/
this.cont = null;
/**
* Left-side inner container DOM element (rows counter in toolbar)
* @type {DOMElement}
* @private
*/
this.lCont = null;
/**
* Right-side inner container DOM element (reset button,
* page length selector in toolbar)
* @type {DOMElement}
* @private
*/
this.rCont = null;
/**
* Middle inner container DOM element (paging elements in toolbar)
* @type {DOMElement}
* @private
*/
this.cCont = null;
/**
* Container elements inside toolbar
* @private
*/
this.innerCont = {
left: null,
center: null,
right: null
};
this.emitter.on(EVENTS,
(feature, isExternal) => this.init(isExternal));
/** @inherited */
this.enabled = true;
}
/**
* Initialize toolbar components
* @param {Boolean} isExternal initialize only if component belongs
* to toolbar
*/
init(isExternal) {
if (this.initialized || isExternal) {
return;
}
let tf = this.tf;
// default container
let container = createElm('div');
container.className = this.contCssClass;
// custom container
if (this.tgtId) {
elm(this.tgtId).appendChild(container);
}
// grid-layout
else if (tf.gridLayout) {
let gridLayout = tf.Mod.gridLayout;
gridLayout.tblMainCont.appendChild(container);
container.className = gridLayout.infDivCssClass;
}
// default location: just above the table
else {
let cont = createElm('caption');
cont.appendChild(container);
tf.dom().insertBefore(cont, tf.dom().firstChild);
}
this.cont = container;
// left container
this.lCont = this.createContainer(container, this.lContCssClass);
// right container
this.rCont = this.createContainer(container, this.rContCssClass);
// middle container
this.cCont = this.createContainer(container, this.cContCssClass);
this.innerCont = {
left: this.lCont,
center: this.cCont,
right: this.rCont
};
/** @inherited */
this.initialized = true;
// emit help initialisation only if undefined
if (isUndef(tf.help)) {
// explicitily enable help to initialise feature by
// default, only if setting is undefined
tf.Mod.help.enable();
this.emitter.emit('init-help', tf);
}
}
/**
* Return the container based on requested position inside the toolbar
* @param {String} [position=RIGHT] 3 possible positions: 'left', 'center',
* 'right'
* @param {DOMElement} el optional DOM element to be inserter in container
* @returns {DOMElement}
*/
container(position = RIGHT, el) {
let cont = this.innerCont[position];
if (el) {
cont.appendChild(el);
}
return cont;
}
/**
* Create DOM element inside passed container
* @param {DOMElement} container
* @param {String} css
* @private
*/
createContainer(container, css) {
let div = createElm('div', ['class', css]);
container.appendChild(div);
return div;
}
/**
* Destroy Toolbar instance
*/
destroy() {
if (!this.initialized) {
return;
}
let tf = this.tf;
removeElm(this.cont);
this.cont = null;
let tbl = tf.dom();
let captions = tag(tbl, 'caption');
[].forEach.call(captions, (el) => removeElm(el));
/** @inherited */
this.initialized = false;
}
}
// TODO: remove as soon as feature name is fixed
Toolbar.meta = {alwaysInstantiate: true};

View file

@ -1,32 +0,0 @@
import {isNumber} from './types';
/**
* Takes a string, removes all formatting/cruft and returns the raw float value
* @param {String} Formatted number
* @param {String} Decimal type '.' or ','
* @return {Number} Unformatted number
*
* https://github.com/openexchangerates/accounting.js/blob/master/accounting.js
*/
export const parse = (value, decimal = '.') => {
// Return the value as-is if it's already a number
if (isNumber(value)) {
return value;
}
// Build regex to strip out everything except digits, decimal point and
// minus sign
let regex = new RegExp('[^0-9-' + decimal + ']', ['g']);
let unformatted = parseFloat(
('' + value)
// replace bracketed values with negatives
.replace(/\((.*)\)/, '-$1')
// strip out any cruft
.replace(regex, '')
// make sure decimal point is standard
.replace(decimal, '.')
);
// This will fail silently
return !isNaN(unformatted) ? unformatted : 0;
};

View file

@ -1,6 +0,0 @@
/**
* Export window or global object depending on the environment
*/
export const root = (typeof self === 'object' && self.self === self && self) ||
(typeof global === 'object' && global.global === global && global) ||
this;

View file

@ -1,53 +0,0 @@
import {isBoolean, isString, isFn, isArray} from './types';
/** Configuration settings helpers */
/**
* If passed value is not of boolean type return the default value
* otherwise return the value itself
* @param {Boolean|Any} value
* @param {Boolean} default value
* @return {Boolean|Any}
*/
export const defaultsBool =
(val, defaultVal) => isBoolean(val) ? val : defaultVal;
/**
* If passed value is not of string type return the default value
* otherwise return the value itself
* @param {String|Any} value
* @param {String} default value
* @return {String|Any}
*/
export const defaultsStr =
(val, defaultVal) => isString(val) ? val : defaultVal;
/**
* If passed value is not of number type return the default value
* otherwise return the value itself
* @param {Number|Any} value
* @param {Number} default value
* @return {Number|Any}
*/
export const defaultsNb =
(val, defaultVal) => isNaN(val) ? defaultVal : val;
/**
* If passed value is not of array type return the default value
* otherwise return the value itself
* @param {Array|Any} value
* @param {Array} default value
* @return {Array|Any}
*/
export const defaultsArr =
(val, defaultVal) => isArray(val) ? val : defaultVal;
/**
* If passed value is not of function type return the default value
* otherwise return the value itself
* @param {Function|Any} value
* @param {Function} default value
* @return {Function|Any}
*/
export const defaultsFn =
(val, defaultVal) => isFn(val) ? val : defaultVal;

View file

@ -1,82 +1,15 @@
import {parse as parseNb} from './number';
import {Date as SugarDate} from 'sugar-date';
import Str from './string';
/** Sorting utilities */
/**
* Case insensitive compare function for passed strings
* @param {String} First string
* @param {String} Second string
* @return {Number} -1 if first string lower than second one
* 0 if first string same order as second one
* 1 if first string greater than second one
*/
export const ignoreCase = (a, b) => {
let x = a.toLowerCase();
let y = b.toLowerCase();
return x < y ? -1 : (x > y ? 1 : 0);
};
/**
* Compare function for sorting passed numbers in ascending manner
* @param {Number} First number
* @param {Number} Second number
* @return {Number} Negative, zero or positive number
*/
export const numSortAsc = (a, b) => (a - b);
/**
* Compare function for sorting passed numbers in descending manner
* @param {Number} First number
* @param {Number} Second number
* @return {Number} Negative, zero or positive number
*/
export const numSortDesc = (a, b) => (b - a);
/**
* Compare function for sorting passed dates in ascending manner according to
* the corresponding UTC numeric value (returned by getTime)
* @param {Date} First date object
* @param {Date} Second date object
* @return {Number} Negative, zero or positive number
*/
export const dateSortAsc = (date1, date2) => date1.getTime() - date2.getTime();
/**
* Compare function for sorting passed dates in descending manner according to
* the corresponding UTC numeric value (returned by getTime)
* @param {Date} First date object
* @param {Date} Second date object
* @return {Number} Negative, zero or positive number
*/
export const dateSortDesc = (date1, date2) => date2.getTime() - date1.getTime();
/**
* Curried compare function for sorting passed formatted numbers in desired
* fashion according to supplied compare function and decimal separator
* @param {Function} Compare function
* @param {String} [decimal=','] Decimal separator
* @return {Function} Compare function receiving parsed numeric arguments
*/
export const sortNumberStr = (compareFn, decimal = ',') => {
return (numStr1, numStr2) => {
let num1 = parseNb(numStr1, decimal);
let num2 = parseNb(numStr2, decimal);
return compareFn(num1, num2);
};
};
/**
* Curried compare function for sorting passed formatted dates in desired
* fashion according to supplied compare function and locale
* @param {Function} Compare function
* @param {String} [locale='en-us'] Locale code
* @return {Function} Compare function receiving parsed date arguments
*/
export const sortDateStr = (compareFn, locale = 'en-us') => {
return (dateStr1, dateStr2) => {
let date1 = SugarDate.create(dateStr1, locale);
let date2 = SugarDate.create(dateStr2, locale);
return compareFn(date1, date2);
};
export default {
ignoreCase(a, b){
let x = Str.lower(a);
let y = Str.lower(b);
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
},
numSortAsc(a, b){
return (a - b);
},
numSortDesc(a, b){
return (b - a);
}
};

View file

@ -1,112 +1,61 @@
import {remove as removeDiacritics} from 'diacritics';
/**
* String utilities
*/
/**
* Removes whitespace from both sides of passed string
* @param {String} text
* @return {String}
*/
export const trim = (text) => {
if (text.trim) {
return text.trim();
}
return text.replace(/^\s*|\s*$/g, '');
};
export default {
/**
* Checks if passed string is empty
* @param {String} text
* @return {Boolean}
*/
export const isEmpty = (text) => trim(text) === '';
/**
* Makes regex safe string by escaping special characters from passed string
* @param {String} text
* @return {String} escaped string
*/
export const rgxEsc = (text) => {
let chars = /[-\/\\^$*+?.()|[\]{}]/g;
let escMatch = '\\$&';
return String(text).replace(chars, escMatch);
};
/**
* Returns passed string as lowercase if caseSensitive flag set false. By
* default it returns the string with no casing changes.
* @param {String} text
* @return {String} string
*/
export const matchCase = (text, caseSensitive = false) => {
if (!caseSensitive) {
lower(text){
return text.toLowerCase();
}
return text;
};
},
/**
* Checks if passed data contains the searched term
* @param {String} term Searched term
* @param {String} data Data string
* @param {Boolean} exactMatch Exact match
* @param {Boolean} caseSensitive Case sensitive
* @param {Boolean} ignoreDiacritics Ignore diacritics
* @return {Boolean}
*/
export const contains = (term, data, exactMatch = false, caseSensitive = false,
ignoreDiacritics = false) => {
// Improved by Cedric Wartel (cwl) automatic exact match for selects and
// special characters are now filtered
let regexp;
let modifier = caseSensitive ? 'g' : 'gi';
if (ignoreDiacritics) {
term = removeDiacritics(term);
data = removeDiacritics(data);
}
if (exactMatch) {
regexp = new RegExp('(^\\s*)' + rgxEsc(term) + '(\\s*$)',
modifier);
} else {
regexp = new RegExp(rgxEsc(term), modifier);
}
return regexp.test(data);
};
upper(text){
return text.toUpperCase();
},
/**
* Camelize a string, cutting the string by multiple separators like
* hyphens, underscores and spaces.
* @param {String} text text to camelize
* @return {String} camelized text
*/
export const toCamelCase = (text = '') => {
return text.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => {
if (p2) {
return p2.toUpperCase();
trim(text){
if (text.trim){
return text.trim();
}
return p1.toLowerCase();
});
};
return text.replace(/^\s*|\s*$/g, '');
},
/**
* Generate a string in the format of a UUID (Universally Unique IDentifier).
* NOTE: This format of 8 chars, followed by 3 groups of 4 chars, followed by 12
* chars is known as a UUID and is defined in RFC4122 and is a standard for
* generating unique IDs. This function DOES NOT implement this standard.
* It simply outputs a string that looks similar. The standard is found here:
* https://www.ietf.org/rfc/rfc4122.txt
* source: https://gist.github.com/gordonbrander/2230317
* @return {String}
*/
export const uuid = () => {
const chr4 = () => Math.random().toString(16).slice(-4);
isEmpty(text){
return this.trim(text) === '';
},
rgxEsc(text){
let chars = /[-\/\\^$*+?.()|[\]{}]/g;
let escMatch = '\\$&';
return String(text).replace(chars, escMatch);
},
matchCase(text, caseSensitive){
if(!caseSensitive){
return this.lower(text);
}
return text;
},
/**
* Checks if passed data contains the searched term
* @param {String} term Searched term
* @param {String} data Data string
* @param {Boolean} exactMatch Exact match
* @param {Boolean} caseSensitive Case sensitive
* @return {Boolean}
*/
contains(term, data, exactMatch=false, caseSensitive=false){
// Improved by Cedric Wartel (cwl) automatic exact match for selects and
// special characters are now filtered
let regexp,
modifier = caseSensitive ? 'g' : 'gi';
if(exactMatch){
regexp = new RegExp(
'(^\\s*)'+ this.rgxEsc(term) +'(\\s*$)', modifier);
} else {
regexp = new RegExp(this.rgxEsc(term), modifier);
}
return regexp.test(data);
}
return chr4() + chr4()
+ '-' + chr4()
+ '-' + chr4()
+ '-' + chr4()
+ '-' + chr4()
+ chr4() + chr4();
};

File diff suppressed because it is too large Load diff

View file

@ -1,81 +1,71 @@
/**
* Types utilities
*/
const UNDEFINED = void 0;
/**
* Return an empty function
* @return {Function}
*/
export const EMPTY_FN = function() {};
export default {
/**
* Check if argument is an object or a global object
* @param {String or Object} v
* @return {Boolean}
*/
isObj(v){
let isO = false;
if(typeof v === 'string'){
if(window[v] && typeof window[v] === 'object'){
isO = true;
}
} else {
if(v && typeof v === 'object'){
isO = true;
}
}
return isO;
},
/**
* Check passed argument is an object
* @param {Object} obj
* @return {Boolean}
*/
export const isObj =
(obj) => Object.prototype.toString.call(obj) === '[object Object]';
/**
* Check if argument is a function
* @param {Function} fn
* @return {Boolean}
*/
isFn(fn){
return (fn && fn.constructor == Function);
},
/**
* Check passed argument is a function
* @param {Function} obj
* @return {Boolean}
*/
export const isFn =
(obj) => Object.prototype.toString.call(obj) === '[object Function]';
/**
* Check if argument is an array
* @param {Array} obj
* @return {Boolean}
*/
isArray(obj){
return (obj && obj.constructor == Array);
},
/**
* Check passed argument is an array
* @param {Array} obj
* @return {Boolean}
*/
export const isArray =
(obj) => Object.prototype.toString.call(obj) === '[object Array]';
/**
* Determine if argument is undefined
* @param {Any} o
* @return {Boolean}
*/
isUndef(o){
return o === UNDEFINED;
},
/**
* Check passed argument is a string
* @param {String} obj obj
* @returns {Boolean}
*/
export const isString =
(obj) => Object.prototype.toString.call(obj) === '[object String]';
/**
* Determine if argument is null
* @param {Any} o
* @return {Boolean}
*/
isNull(o){
return o === null;
},
/**
* Check passed argument is a number
* @param {Number} obj
* @returns {Boolean}
*/
export const isNumber =
(obj) => Object.prototype.toString.call(obj) === '[object Number]';
/**
* Check passed argument is a boolean
* @param {Boolean} obj
* @returns {Boolean}
*/
export const isBoolean =
(obj) => Object.prototype.toString.call(obj) === '[object Boolean]';
/**
* Check passed argument is undefined
* @param {Any} obj
* @return {Boolean}
*/
export const isUndef = (obj) => obj === UNDEFINED;
/**
* Check passed argument is null
* @param {Any} obj
* @return {Boolean}
*/
export const isNull = (obj) => obj === null;
/**
* Check passed argument is empty (undefined, null or empty string)
* @param {Any} obj
* @return {Boolean}
*/
export const isEmpty = (obj) => isUndef(obj) || isNull(obj) || obj.length === 0;
/**
* Determine if argument is empty (undefined, null or empty string)
* @param {Any} o
* @return {Boolean}
*/
isEmpty(o){
return this.isUndef(o) || this.isNull(o) || o.length===0;
}
};

View file

@ -350,4 +350,4 @@
<td>88.233.19.89</td>
</tr>
</tbody>
</table>
</table>

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,3 @@
.activeHeader
background-color #66AFE9 !important
color #fff !important
.activeCell
background-color rgba(0,0,0,0.075)

View file

@ -34,7 +34,7 @@ div.colVisCont
z-index 10000
padding 10px 10px 10px 10px
text-align left
font-size inherit
font-size 12px
p
margin 6px auto 6px auto
@ -47,7 +47,7 @@ div.colVisCont
ul.cols_checklist
padding 0
margin 0
list-style-type none
list-style none
label
display block

View file

@ -11,12 +11,11 @@ $item-font-color = #fff
.div_checklist
width 100%
height 90px
line-height 30px
border 1px solid $filter-border-color
overflow auto
text-align left
background-color $filter-bg-color
color $filter-font-color
color: $filter-font-color
ul.flt_checklist
padding 0 !important
@ -27,20 +26,19 @@ $item-font-color = #fff
li.flt_checklist_item
padding 1px !important
margin 0 !important
font-size inherit
font-size 10px !important
border-bottom 1px solid $filter-border-color !important
&:hover
background-color $item-bg-color !important
color $item-font-color !important
color $item-font-color !important;
label
display block !important
font-weight inherit !important
display block !important;
input
vertical-align middle !important
margin 2px 5px 3px 1px !important
vertical-align middle !important;
margin 2px 5px 2px 1px !important;
// disabled checklist item
.flt_checklist_item_disabled

View file

@ -43,7 +43,6 @@
// input and select filter type
.flt
font-family inherit
font-size inherit
display block
color $filter-font-color
background-color $filter-bg-color
@ -65,7 +64,6 @@
// multiple select type filter
select.flt_multi
font-family inherit
font-size inherit
color $filter-font-color
background-color $filter-bg-color
border 1px solid $filter-border-color
@ -76,10 +74,6 @@ select.flt_multi
vertical-align middle
box-sizing border-box
option
padding-top 5px
padding-bottom 5px
// tiny input type filter
.flt_s
@extend .flt
@ -94,7 +88,7 @@ select.flt_multi
box-sizing initial
display initial
// pop-up filters elements
// Pop-up filters elements
div.popUpFilter
arrow(top, 10px white, 2px $th-bg-color)
box-shadow(3px 3px 2px #888)
@ -105,7 +99,3 @@ div.popUpFilter
background-color $th-bg-color
border 1px solid $filter-row-bg-color
padding 0
// pop-up container
.popUpPlaceholder
position relative

View file

@ -11,6 +11,7 @@ div.grd_Cont
width 800px
height auto
overflow hidden
padding 3px 3px 3px 3px
background-color $grid-layout-bg-color
border 1px solid $grid-layout-border-color
@ -36,9 +37,6 @@ div.grd_Cont
.no-results
background-color transparent
.sort-arrow
position initial
// content table container
div.grd_tblCont
height 400px
@ -49,9 +47,8 @@ div.grd_tblCont
// headers' table container
div.grd_headTblCont
display block
margin-right 20px
height auto
width 800px
overflow hidden
border-bottom 1px solid $grid-layout-border-color
background-color $grid-layout-bg-color
@ -63,6 +60,10 @@ div.grd_headTblCont table
table-layout fixed
box-sizing initial
div.grd_tblCont table
border-right 1px solid $grid-layout-border-color
box-sizing initial
// headers
div.grd_tblCont table th
div.grd_headTblCont table th

View file

@ -32,7 +32,6 @@ div.helpCont
color #333
background #fff
text-align left
z-index 1000
a
color #cc0000

View file

@ -1,7 +0,0 @@
/**
* Empty content mixin
*/
empty-content()
&:empty:after
content "\00A0"

View file

@ -14,8 +14,3 @@ input.reset
&:hover
background-color $toolbar-hover-color
a.reset
font-weight normal !important
line-height $min-height
padding 5px 5px

View file

@ -3,18 +3,14 @@
*/
.sort-arrow
position absolute
display none
width 11px
height 11px
margin 0
margin 0 2px
background-position center center;
background-repeat no-repeat
&.descending
display inline
background-image url("themes/downsimple.png")
background-image url("themes/downsimple.png")
&.ascending
display inline
background-image url("themes/upsimple.png")
background-image url("themes/upsimple.png")

View file

@ -26,19 +26,3 @@ table.TF
margin 0
padding $padding
border-bottom 1px solid $td-border-color
text-overflow ellipsis
// responsive
&.resp
display block
overflow-x auto
overflow-y hidden
.sort-arrow
position initial
// stick headers
&.sticky
th
position sticky
top -1px

View file

@ -1,18 +0,0 @@
/** Main stylesheet */
@import 'active-header'
@import 'alternating-rows'
@import 'ezedittable'
@import 'filter-checklist'
@import 'filters'
@import 'grid-layout'
@import 'help'
@import 'keyword'
@import 'loader'
@import 'noresults'
@import 'paging'
@import 'reset-button'
@import 'rows-counter'
@import 'sort'
@import 'status-bar'
@import 'table'
@import 'toolbar'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 B

View file

@ -1,205 +0,0 @@
/**
* transparent theme overrides
*/
@import '../../mixins/box-shadow'
// table
table.TF
padding 0
color inherit
border-right 1px solid transparent
border-top 1px solid transparent
border-left 1px solid transparent
border-bottom 0
th
margin 0
color inherit
background-color transparent
border-color transparent
border-width 1px
border-style solid
&:last-child
border-right 1px solid transparent
td
margin 0
padding 5px
color inherit
border-bottom 1px solid transparent
border-left 0
border-top 0
border-right 0
// filters
.fltrow
background-color transparent
th, td
padding 1px 3px 1px 3px
border-bottom 1px solid transparent !important
&:last-child
border-right 1px solid transparent
.flt,
select.flt,
select.flt_multi,
.flt_s,
.single_flt,
.div_checklist
border 1px solid #A4BED4
input.flt
width 99% !important
// toolbar
.inf
background-color transparent
border 1px solid transparent
height $min-height
color inherit
div.tot,
div.status
border-right 0 !important
// help
.helpBtn
&:hover
background-color transparent
// reset button
input.reset
background transparent url(images/icn_clear_filters.png) center center no-repeat !important
.nextPage
background transparent url(images/btn_next_page.gif) center center no-repeat !important
border 1px solid transparent !important
&:hover
background #F7F7F7 url(images/btn_next_page.gif) center center no-repeat !important
border 1px solid #F7F7F7 !important
.previousPage
background transparent url(images/btn_prev_page.gif) center center no-repeat !important
border 1px solid transparent !important
&:hover
background #F7F7F7 url(images/btn_prev_page.gif) center center no-repeat !important
border 1px solid #F7F7F7 !important
.firstPage
background transparent url(images/btn_first_page.gif) center center no-repeat !important
border 1px solid transparent !important
&:hover
background #F7F7F7 url(images/btn_first_page.gif) center center no-repeat !important
border 1px solid #F7F7F7 !important
.lastPage
background transparent url(images/btn_last_page.gif) center center no-repeat !important
border 1px solid transparent !important
&:hover
background #F7F7F7 url(images/btn_last_page.gif) center center no-repeat !important
border 1px solid #F7F7F7 !important
// active header
.activeHeader
background #F7F7F7 !important
border 1px solid transparent
color inherit !important
// grid-layout
// main container
div.grd_Cont
box-shadow(0 0 0 0 rgba(50, 50, 50, 0.75))
background-color transparent
border 1px solid transparent
padding 0 !important
// alternating background colors
.even
background-color transparent
.odd
background-color #F7F7F7
// headers' table container
div.grd_headTblCont
background-color transparent
border-bottom none !important
div.grd_tblCont table
border-right none !important
// headers
div.grd_tblCont table th,
div.grd_headTblCont table th,
div.grd_headTblCont table td
background transparent
border-bottom 1px solid transparent
border-right 1px solid transparent !important
border-left 1px solid transparent
border-top 1px solid transparent
div.grd_tblCont table td
border-bottom 1px solid transparent
border-right 0 !important
border-left 0 !important
border-top 0 !important
// toolbar containing left, middle and right divs
.grd_inf
background-color transparent
color inherit
border-top 1px solid transparent
a
text-decoration none
font-weight bold
// loader
.loader
background-color #F7F7F7
border 1px solid #F7F7F7
border-radius 5px
color #000000
text-shadow none
// alternating background colors
.even
background-color transparent
.odd
background-color #F7F7F7
// filters visibility
span.expClpFlt
a.btnExpClpFlt
&:hover
background-color transparent !important
// ezedittable
// selection
.ezActiveRow
background-color #CCCCCC !important
color inherit
.ezSelectedRow
background-color #CCCCCC !important
color inherit
.ezActiveCell
background-color transparent
color inherit
font-weight bold
.ezETSelectedCell
background-color transparent
font-weight bold
color inherit

View file

@ -3,14 +3,12 @@
*/
@import 'config'
@import 'mixins/empty-content'
// left, middle and right divs container (toolbar)
.inf
clear both
width auto
height $min-height
min-width 400px
background-color $toolbar-bg-color
font-size $toolbar-font-size
margin 0
@ -34,25 +32,25 @@
// left div
.ldiv
empty-content()
float left
width 30%
// height 100%
position inherit
text-align left
// middle div
.mdiv
empty-content()
float left
width 38%
// height 100%
position inherit
text-align center
padding 0
// right div
.rdiv
empty-content()
float right
width 30%
// height 100%
position inherit
text-align right

View file

@ -49,9 +49,8 @@
<script data-config>
var filtersConfig = {
base_path: '../dist/tablefilter/',
grid_layout: {
width: '900px'
},
grid_layout: true,
grid_width: '900px',
alternate_rows: true,
btn_reset: true,
rows_counter: true,

View file

@ -19,18 +19,15 @@
<script data-config>
var filtersConfig = {
base_path: '../dist/tablefilter/',
auto_filter: {
delay: 750 // milliseconds
},
auto_filter: true,
auto_filter_delay: 1100, //milliseconds
filters_row_index: 1,
state: true,
remember_grid_values: true,
alternate_rows: true,
rows_counter: {
text: 'Rows: '
},
rows_counter: true,
rows_counter_text: 'Rows: ',
btn_reset: true,
status_bar: true,
sticky_headers: true,
msg_filter: 'Filtering...'
};

Some files were not shown because too many files have changed in this diff Show more