Import 0ad_0.0.26-1.debian.tar.xz
authorLudovic Rousseau <rousseau@debian.org>
Sun, 2 Oct 2022 16:14:33 +0000 (17:14 +0100)
committerLudovic Rousseau <rousseau@debian.org>
Sun, 2 Oct 2022 16:14:33 +0000 (17:14 +0100)
[dgit import tarball 0ad 0.0.26-1 0ad_0.0.26-1.debian.tar.xz]

54 files changed:
0ad.6 [new file with mode: 0644]
changelog [new file with mode: 0644]
control [new file with mode: 0644]
copyright [new file with mode: 0644]
docs [new file with mode: 0644]
install [new file with mode: 0755]
manpages [new file with mode: 0644]
missing-sources/README.txt [new file with mode: 0644]
missing-sources/jquery.colorhelpers.js [new file with mode: 0644]
missing-sources/jquery.event.drag.js [new file with mode: 0644]
missing-sources/jquery.mousewheel.js [new file with mode: 0644]
missing-sources/tablefilter/array.js [new file with mode: 0644]
missing-sources/tablefilter/cookie.js [new file with mode: 0644]
missing-sources/tablefilter/date.js [new file with mode: 0644]
missing-sources/tablefilter/dom.js [new file with mode: 0644]
missing-sources/tablefilter/event.js [new file with mode: 0644]
missing-sources/tablefilter/extensions/advancedGrid/adapterEzEditTable.js [new file with mode: 0644]
missing-sources/tablefilter/extensions/advancedGrid/advancedGrid.js [new file with mode: 0644]
missing-sources/tablefilter/extensions/colOps/colOps.js [new file with mode: 0644]
missing-sources/tablefilter/extensions/colsVisibility/colsVisibility.js [new file with mode: 0644]
missing-sources/tablefilter/extensions/filtersVisibility/filtersVisibility.js [new file with mode: 0644]
missing-sources/tablefilter/extensions/sort/adapterSortabletable.js [new file with mode: 0644]
missing-sources/tablefilter/extensions/sort/sort.js [new file with mode: 0644]
missing-sources/tablefilter/helpers.js [new file with mode: 0644]
missing-sources/tablefilter/modules/alternateRows.js [new file with mode: 0644]
missing-sources/tablefilter/modules/checkList.js [new file with mode: 0644]
missing-sources/tablefilter/modules/clearButton.js [new file with mode: 0644]
missing-sources/tablefilter/modules/dropdown.js [new file with mode: 0644]
missing-sources/tablefilter/modules/feature.js [new file with mode: 0644]
missing-sources/tablefilter/modules/gridLayout.js [new file with mode: 0644]
missing-sources/tablefilter/modules/help.js [new file with mode: 0644]
missing-sources/tablefilter/modules/highlightKeywords.js [new file with mode: 0644]
missing-sources/tablefilter/modules/loader.js [new file with mode: 0644]
missing-sources/tablefilter/modules/paging.js [new file with mode: 0644]
missing-sources/tablefilter/modules/popupFilter.js [new file with mode: 0644]
missing-sources/tablefilter/modules/rowsCounter.js [new file with mode: 0644]
missing-sources/tablefilter/modules/statusBar.js [new file with mode: 0644]
missing-sources/tablefilter/modules/store.js [new file with mode: 0644]
missing-sources/tablefilter/sort.js [new file with mode: 0644]
missing-sources/tablefilter/string.js [new file with mode: 0644]
missing-sources/tablefilter/tablefilter.js [new file with mode: 0644]
missing-sources/tablefilter/types.js [new file with mode: 0644]
patches/Disable-test_regression_rP26522.patch [new file with mode: 0644]
patches/Fix-build-mozjs-on-armhf.patch [new file with mode: 0644]
patches/TestStunClient [new file with mode: 0644]
patches/allow-build-with-root.patch [new file with mode: 0644]
patches/fix-bindir.patch [new file with mode: 0644]
patches/series [new file with mode: 0644]
pyrogenesis.6 [new file with mode: 0644]
rules [new file with mode: 0755]
source/format [new file with mode: 0644]
source/lintian-overrides [new file with mode: 0644]
upstream/metadata [new file with mode: 0644]
watch [new file with mode: 0644]

diff --git a/0ad.6 b/0ad.6
new file mode 100644 (file)
index 0000000..ab71e27
--- /dev/null
+++ b/0ad.6
@@ -0,0 +1,147 @@
+.TH 0AD "6" "April 7, 2012"
+.SH NAME
+0ad \- A real-time strategy game of ancient warfare
+.SH SYNOPSIS
+.B 0ad
+.RI [ options ]
+.SH DESCRIPTION
+0 A.D. is a free, open-source, cross-platform real-time strategy game
+of ancient warfare. As the military leader of an ancient civilisation,
+you must gather the resources you need to raise a military force
+capable of dominating your enemies.
+.SH OPTIONS
+.SS Basic gameplay
+.TP
+.B \-autostart
+Load a map instead of showing main menu (see below).
+.TP
+.B \-editor
+Launch the Atlas scenario editor.
+.TP
+.B \-mod NAME
+Start the game using NAME mod.
+.TP
+.B \-quickstart
+Load faster (disables audio and some system info logging).
+.SS Autostart
+.TP
+.B \-autostart=NAME
+Map NAME for scenario, or rms name for random map.
+.TP
+.B \-autostart-ai=PLAYER:AI
+Adds named AI to the given PLAYER (e.g. 2:testbot).
+.SS Multiplayer
+.TP
+.B \-autostart-playername=NAME
+Multiplayer local player NAME (default 'anonymous').
+.TP
+.B \-autostart-host
+Multiplayer host mode.
+.TP
+.B \-autostart-players=NUMBER
+Multiplayer host: NUMBER of client players (default 2).
+.TP
+.B \-autostart-client
+Multiplayer client mode.
+.TP
+.B \-autostart-ip=IP
+Multiplayer client: connect to this host IP.
+.SS Random maps only
+.TP
+.B \-autostart-random
+Random map.
+.TP
+.B \-autostart-random=SEED
+Random map with SEED value (default 0, use \-1 for random).
+.TP
+.B \-autostart-size=TILES
+Random map SIZE in tiles (default 192).
+.TP
+.B \-autostart-players=NUMBER
+NUMBER of players on random map.
+.SS Configuration
+.TP
+.B \-conf:KEY=VALUE
+Set a config value (overrides the contents of system.cfg).
+.TP
+.B \-g=F
+Set the gamma correction to 'F' (default 1.0).
+.TP
+.B \-nosound
+Disable audio.
+.TP
+.B \-onlyPublicFiles
+Force game to use only the public (default) mod.
+.TP
+.B \-shadows
+Enable shadows.
+.TP
+.B \-vsync
+Enable VSync, i.e. lock FPS to monitor refresh rate.
+.TP
+.B \-xres=N
+Set screen X resolution to 'N'.
+.TP
+.B \-yres=N
+Set screen Y resolution to 'N'.
+.SS Advanced / diagnostic
+.TP
+.B \-dumpSchema
+Creates a file entity.rng in the working directory, containing
+complete entity XML schema, used by various analysis tools.
+.TP
+.B \-replay=PATH
+Non-visual replay of a previous game, used for analysis purposes.
+PATH is system path to commands.txt containing simulation log.
+.TP
+.B \-writableRoot
+Store runtime game data in root data directory (only use if you
+have write permissions on that directory).
+.SS Archive builder
+.TP
+.B \-archivebuild=PATH
+System PATH of the base directory containing mod data to be
+archived/precached.
+.TP
+.B \-archivebuild-output=PATH
+System PATH to output of the resulting .zip archive (use with
+archivebuild).
+.TP
+.B \-archivebuild-compress
+Enable deflate compression in the .zip (no zip compression by
+default since it hurts compression of release packages).
+
+.SH "FILES"
+.TP
+.B ~/.config/0ad/config/local.cfg
+User specific settings. You may put in this file any user specific
+settings using the same syntax of the default settings file.
+.PP
+0 A.D. also uses the 
+.B ~/.cache/0ad/
+,
+.B ~/.config/0ad/
+and
+.B ~/.local/share/0ad/
+directories for other user specific files, such as texture cache,
+screenshots and saved games.
+
+.SH SEE ALSO
+.TP
+.B http://wildfiregames.com/0ad/
+0 A.D. main web site.
+.TP
+.B http://trac.wildfiregames.com/
+0 A.D. wiki and developer web site.
+
+.SH AUTHOR
+0ad was written by the 0 A.D. Team.
+.PP
+This manual page was written by Fabio Pedretti <fabio.ped@libero.it>,
+for the Debian project (and may be used by others). Permission is
+granted to copy, distribute and/or modify this document under the
+terms of the GNU General Public License, Version 2 or any later version
+published by the Free Software Foundation.
+.PP
+On Debian systems, the complete text of the GNU General Public License
+can be found in /usr/share/common-licenses/GPL-2.
diff --git a/changelog b/changelog
new file mode 100644 (file)
index 0000000..93cd6b4
--- /dev/null
+++ b/changelog
@@ -0,0 +1,460 @@
+0ad (0.0.26-1) unstable; urgency=medium
+
+  * New upstream release.
+  * Fix "New upstream release - version 0.0.26" (Closes: #1020649)
+  * d/p/fix-build-mozjs-with-python-3.10.patch: removed because added upstream
+  * d/p/fix-build-atlas-gcc11-glibc-2.35.patch: removed because added upstream
+  * d/control: add libfreetype-dev in Build-Depends: to fix build error
+  * d/p/Disable-test_regression_rP26522.patch: remove test
+  * d/copyright: update file list and fix lintian warnings
+  * d/watch: upgrade version and fix lintian warning
+  * d/control: update Standards-Version: 4.6.1. No change needed
+  * d/rules: remove use of --dbgsym-migration=
+  * d/contol: upgrade debhelper-compat from 12 to 13
+  * d/s/lintian-overrides: rename tag insane-line-length-in-source-file
+
+ -- Ludovic Rousseau <rousseau@debian.org>  Sun, 02 Oct 2022 18:14:33 +0200
+
+0ad (0.0.25b-2) unstable; urgency=medium
+
+  * Team upload
+  * Add patches from Ubuntu (Closes: #1008075)
+    - d/p/fix-build-mozjs-with-python-3.10.patch:
+      Add patch from upstream via Ubuntu to fix FTBFS with Python 3.10
+    - d/p/fix-build-atlas-gcc11-glibc-2.35.patch:
+      Add patch from upstream via Ubuntu to fix future FTBFS with glibc 2.35
+
+ -- Simon McVittie <smcv@debian.org>  Mon, 28 Mar 2022 12:29:22 +0100
+
+0ad (0.0.25b-1.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * Fix building mozjs on armhf (Closes: #1001882)
+
+ -- Shengjing Zhu <zhsj@debian.org>  Thu, 23 Dec 2021 01:34:12 +0800
+
+0ad (0.0.25b-1) unstable; urgency=medium
+
+  [David W. Kennedy <dave_k@reasoned.us>]
+  * Package new upstream release.
+  * d/control: Update required versions of dependencies.
+  * d/copyright: Update and correct copyright information of debian package
+    and embedded libraries
+  * d/install: Install binaries/system/readme.txt as README.command-line.txt,
+    also mark install executable to support renaming README.txt with dh-exec.
+  * d/rules: Clean up build files for libnvtt and spidermonkey in
+    dh_auto_clean.
+  * d/rules: Exclude libmozjs78-ps-release.so from dh_dwz in order to work
+    around a crash in dwz.
+  * d/source/local-options: Abort on changes to the upstream source code
+    before committing to the upstream branch of Debian Salsa VCS
+  * d/watch: Update URL for releases.
+
+  [ Ludovic Rousseau ]
+  * Fix "New upstream release of 0ad - version 0.0.25" (Closes: #992017)
+  * d/control: use https:// for Homepage: URL
+  * d/install: make the script executable
+  * New upstream release 0.0.25b
+  * d/patches/TestStunClient: remove failing test
+
+ -- Ludovic Rousseau <rousseau@debian.org>  Fri, 27 Aug 2021 15:28:30 +0200
+
+0ad (0.0.24b-2) UNRELEASED; urgency=medium
+
+  [ Pino Toscano ]
+  * Install the AppStream file to /usr/share/metainfo, as /usr/share/appdata
+    is a long time deprecated location.
+  * Install the application icon in the XDG hicolor icon theme, rather than
+    the legacy pixmaps location.
+
+ -- Debian Games Team <pkg-games-devel@lists.alioth.debian.org>  Tue, 11 May 2021 08:48:59 +0200
+
+0ad (0.0.24b-1) experimental; urgency=medium
+
+  [ Phil Morrell ]
+  * New upstream release.
+  * Drop upstreamed patches.
+  * Update build-deps. (Closes: #936101, #975379)
+
+  [ Ludovic Rousseau ]
+  * version 0.0.24b
+  * d/copyright: remove unused entry
+    lintian: unused-file-paragraph-in-dep5-copyright paragraph at line 72
+  * d/copyright: remove unused entry
+    lintian: unused-file-paragraph-in-dep5-copyright paragraph at line 22
+  * use embedded nvtt since version 2.1.0 is not yet in unstable
+  * Fix "New upstream release 0.0.24" (Closes: #983408)
+
+ -- Ludovic Rousseau <rousseau@debian.org>  Sun, 07 Mar 2021 10:53:17 +0100
+
+0ad (0.0.23.1-5) unstable; urgency=medium
+
+  * Fix FTBFS with gcc-10. (Closes: #956967)
+  * Avoid build-dep on unversioned python2. (Closes: #967118)
+  * Cherrypick patch from upstream to add workaround for L3 cache detection on
+    Ryzen 3000 series CPUs. (Closes: #949699)
+  * Update to dh compat level 12.
+  * Update Standards version to 4.5.0.
+  * Add upstream metadata file.
+
+ -- Vincent Cheng <vcheng@debian.org>  Tue, 18 Aug 2020 02:48:00 -0700
+
+0ad (0.0.23.1-4) unstable; urgency=medium
+
+  * Team upload.
+  * Re-upload source-only to enable migration to testing.
+
+ -- Bruno Kleinert <fuddl@debian.org>  Mon, 05 Aug 2019 18:56:58 +0200
+
+0ad (0.0.23.1-3) unstable; urgency=medium
+
+  * Team upload.
+  * Build against GTK 3 version of wxWidget. Replaced build dependency
+    libwxgtk3.0-dev by libwxgtk3.0-gtk3-dev. (Closes: #933456)
+  * Added NEWS.Debian to document a workaround for a known issue caused by the
+    GTK 3 variant of wxWidget.
+
+ -- Bruno Kleinert <fuddl@debian.org>  Sat, 03 Aug 2019 05:18:37 +0200
+
+0ad (0.0.23.1-2) unstable; urgency=medium
+
+  * d/control: version 0.0.23.1 also needs a new 0ad-data
+
+ -- Ludovic Rousseau <rousseau@debian.org>  Wed, 09 Jan 2019 10:33:09 +0100
+
+0ad (0.0.23.1-1) unstable; urgency=medium
+
+  * New upstream release.
+    - Re-release of 0 A.D. Alpha 23 Ken Wood
+      https://play0ad.com/re-release-of-0-a-d-alpha-23-ken-wood/
+  * d/control: change Depends: to use 0.0.23 since 0ad-data is unchanged.
+  * Fix "Alpha 23 Lobby lag and other multiplayer issues" (Closes: #905470)
+
+ -- Ludovic Rousseau <rousseau@debian.org>  Fri, 04 Jan 2019 11:18:19 +0100
+
+0ad (0.0.23-1) unstable; urgency=medium
+
+  * New upstream release.
+    - Remove enable-hardening-relro.patch and armhf-wchar-signedness.patch;
+      obsolete, applied upstream.
+    - Remove create_empty_dirs.patch and move logic into debian/rules.
+  * Add new build-dep on libsodium-dev (>= 1.0.14).
+  * Restore all supported archs as of 0ad/0.0.21-2, i.e. arm64 and kfreebsd-*.
+  * Update Standards version to 4.1.4, no changes required.
+
+ -- Vincent Cheng <vcheng@debian.org>  Mon, 21 May 2018 23:38:30 -0700
+
+0ad (0.0.22-4) unstable; urgency=medium
+
+  * Create empty directory not stored in git
+  * Add description to a quilt patch
+  * Enable parallel build
+  * Use debhelper version 10 (instead of 9)
+  * Migrate the repository from SVN to GIT
+
+ -- Ludovic Rousseau <rousseau@debian.org>  Wed, 10 Jan 2018 16:27:16 +0100
+
+0ad (0.0.22-3.1) unstable; urgency=medium
+
+  * Non-maintainer upload with maintainers permission.
+  * Add armhf back to architecture list.
+  * Fix "0ad FTBFS with on armhf with gcc 7: error: call of overloaded
+    'abs(unsigned int)' is ambiguous" (Closes: #879071)
+
+ -- Peter Michael Green <plugwash@debian.org>  Tue, 21 Nov 2017 00:15:10 +0000
+
+0ad (0.0.22-3) unstable; urgency=medium
+
+  * remove support of kfreebsd-amd64 and kfreebsd-i386 since auto test fails
+    for 0ad alpha 22 and then 0ad FTBFS.
+  * debian/source/lintian-overrides: remove unused-override
+    outdated-autotools-helper-file.
+    Reported by lintian.
+  * debian/rules: enable hardening=+all
+
+ -- Ludovic Rousseau <rousseau@debian.org>  Sat, 04 Nov 2017 11:04:26 +0100
+
+0ad (0.0.22-2) unstable; urgency=medium
+
+  * Fix "0ad FTBFS with on armhf with gcc 7: error: call of overloaded
+    'abs(unsigned int)' is ambiguous" by removing support of armhf
+    (Closes: #879071)
+  * remove support of arm64 because of FTBFS in spidermonkey/mozjs-38.0.0
+    https://buildd.debian.org/status/fetch.php?pkg=0ad&arch=arm64&ver=0.0.22-1&stamp=1508351579&raw=0
+
+ -- Ludovic Rousseau <rousseau@debian.org>  Fri, 27 Oct 2017 16:22:15 +0200
+
+0ad (0.0.22-1) unstable; urgency=medium
+
+  * New upstream release.
+  * Fix "New Release: 0 A.D. Alpha 22 Venustas" (Closes: #872654)
+  * Export SHELL to make mozjs-38 build using debuild(1)
+
+ -- Ludovic Rousseau <rousseau@debian.org>  Wed, 18 Oct 2017 15:33:17 +0200
+
+0ad (0.0.21-2) unstable; urgency=medium
+
+  * Fix FTBFS by installing missing files with dh_install.
+
+ -- Vincent Cheng <vcheng@debian.org>  Sat, 12 Nov 2016 22:57:56 -0800
+
+0ad (0.0.21-1) unstable; urgency=medium
+
+  * New upstream release.
+    - Drop debian/patches/fix-gcc6-segfault.patch, obsolete.
+    - Refresh remaining patches.
+
+ -- Vincent Cheng <vcheng@debian.org>  Sat, 12 Nov 2016 16:46:11 -0800
+
+0ad (0.0.20-3) unstable; urgency=medium
+
+  * Add debian/patches/fix-gcc6-segfault.patch to fix spidermonkey segfault
+    when built with gcc6. (Closes: #835176)
+  * Temporarily disable test suite on arm64 to fix arch-specific FTBFS.
+  * Call dh_strip --dbgsym-migration to cleanly migrate from the old -dbg
+    package to -dbgsym.
+
+ -- Vincent Cheng <vcheng@debian.org>  Wed, 07 Sep 2016 22:44:22 -0700
+
+0ad (0.0.20-2) unstable; urgency=medium
+
+  * Team upload.
+  * Drop 0ad-dbg package and use the automatic -dbgsym package instead.
+  * d/rules: Remove override for dh_builddeb because xz is the default
+    compression.
+  * Declare compliance with Debian Policy 3.9.8.
+  * Build-depend on python and fix FTBFS.
+    Thanks to Lucas Nussbaum for the report. (Closes: #832870)
+  * Ensure that 0ad can be built twice in a row by updating dh_auto_clean
+    target. Thanks to Peter Green for the patch.
+
+ -- Markus Koschany <apo@debian.org>  Sun, 21 Aug 2016 04:54:55 +0200
+
+0ad (0.0.20-1) unstable; urgency=medium
+
+  * New upstream release.
+    - Drop build-dep on libjpeg-dev.
+    - Refresh patches.
+  * Remove debian/menu file as per tech-ctte decision in #741573.
+  * Update Standards version to 3.9.7, no changes required.
+  * Update jQuery/JavaScript related lintian overrides.
+
+ -- Vincent Cheng <vcheng@debian.org>  Sun, 03 Apr 2016 19:42:21 -0700
+
+0ad (0.0.19-1) unstable; urgency=medium
+
+  * New upstream release. (Closes: #807447)
+    - Add support for arm64. (Closes: #790306)
+    - Replace dependency on libsdl1.2-dev with libsdl2-dev (>= 2.0.2).
+    - Remove gcc-5.1.patch, applied upstream.
+    - Refresh remaining patches.
+  * Fix typo in override target in d/rules.
+  * Update watch file.
+
+ -- Vincent Cheng <vcheng@debian.org>  Sun, 20 Dec 2015 19:56:20 -0800
+
+0ad (0.0.18-2) unstable; urgency=medium
+
+  [ Logan Rosen ]
+  * debian/patches/gcc-5.1.patch: Pull patch from upstream Git to fix build
+    with GCC 5.1.
+
+ -- Vincent Cheng <vcheng@debian.org>  Fri, 28 Aug 2015 20:46:34 -0700
+
+0ad (0.0.18-1) unstable; urgency=medium
+
+  * New upstream release.
+    - Refresh patches.
+    - Replace build-dep on libmozjs-24-dev with libnspr4-dev.
+
+ -- Vincent Cheng <vcheng@debian.org>  Wed, 18 Mar 2015 16:58:45 -0700
+
+0ad (0.0.17-1) unstable; urgency=medium
+
+  * New upstream release.
+    - Drop debian/patches/{support_miniupnpc_1.9.patch,fix_gcc4.9_ftbfs.patch}:
+      applied upstream.
+    - Add debian/patches/allow-build-with-root.patch.
+    - Add debian/patches/fix-bindir.patch. (LP: #1380737)
+    - Refresh remaining patches.
+  * Update Standards version to 3.9.6, no changes required.
+
+ -- Vincent Cheng <vcheng@debian.org>  Mon, 13 Oct 2014 20:01:57 -0700
+
+0ad (0.0.16-4) unstable; urgency=medium
+
+  * Add debian/patches/fix_gcc4.9_ftbfs.patch to fix FTBFS when running the
+    test suite when built with gcc 4.9. (Closes: #746822)
+
+ -- Vincent Cheng <vcheng@debian.org>  Tue, 24 Jun 2014 21:21:36 -0700
+
+0ad (0.0.16-3) unstable; urgency=medium
+
+  * Add debian/patches/support_miniupnpc_1.9.patch to fix FTBFS when built
+    with miniupnpc >= 1.9. (Closes: #751224)
+  * Tighten build-dep on libminiupnpc-dev (>= 1.6).
+
+ -- Vincent Cheng <vcheng@debian.org>  Wed, 11 Jun 2014 19:46:46 -0700
+
+0ad (0.0.16-2) unstable; urgency=medium
+
+  * Remove armel from Architecture list in debian/control.
+
+ -- Vincent Cheng <vcheng@debian.org>  Sun, 18 May 2014 19:14:10 -0700
+
+0ad (0.0.16-1) unstable; urgency=medium
+
+  * New upstream release.
+    - Drop debian/patches/fix-kfreebsd-ftbfs.patch; applied upstream.
+  * Avoid repacking tarball by including missing jquery sources in Debian diff,
+    see debian/missing-sources/jquery-1.8.{0,3}.js. Alternative way of fixing
+    #735349.
+    - Update debian/copyright to include missing license entries for the
+      various javascript libraries contained in the source tarball.
+    - Revert changes made to debian/control in 0ad/0.0.15+dfsg-3, i.e.:
+      revert: debian/control: Hardcode versions of 0ad-data{,-common} to
+              depend on to workaround the fact that 0.0.15+dfsg is strictly
+              greater than 0.0.15.
+  * Build-dep on libgloox-dev (>= 1.0.9) to ensure that 0ad is built with
+    newer enough gloox to avoid lobby connectivity issues.
+  * Add new build-deps: libicu-dev, libmozjs-24-dev.
+
+ -- Vincent Cheng <vcheng@debian.org>  Sat, 17 May 2014 16:30:39 -0700
+
+0ad (0.0.15+dfsg-3) unstable; urgency=medium
+
+  * Repack tarball to remove minified javascript files without source in
+    source/tools/jsdebugger/. (Closes: #735349)
+    - debian/control: Hardcode versions of 0ad-data{,-common} to depend on to
+      workaround the fact that 0.0.15+dfsg is strictly greater than 0.0.15.
+  * Update debian/copyright to include text of IBM Common Public License.
+  * Update email address.
+
+ -- Vincent Cheng <vcheng@debian.org>  Tue, 18 Feb 2014 23:21:11 -0800
+
+0ad (0.0.15-2) unstable; urgency=medium
+
+  * Add debian/patches/fix-kfreebsd-ftbfs.patch to fix FTBFS on kfreebsd.
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Fri, 27 Dec 2013 15:03:44 -0800
+
+0ad (0.0.15-1) unstable; urgency=low
+
+  * New upstream release.
+    - Remove fix-arm-ftbfs.patch, fixed upstream.
+  * Add build-dep on libgloox-dev and libminiupnpc-dev.
+  * Append '~' to libnvtt-dev build dependency to ease backporting.
+  * Prefer libwxgtk3.0-dev over libwxgtk2.8-dev as build-dep (keep latter as
+    alternate build-dep to ease backports).
+  * Update Standards version to 3.9.5, no changes required.
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Wed, 25 Dec 2013 17:41:29 -0800
+
+0ad (0.0.14-3) unstable; urgency=low
+
+  * Add debian/patches/fix-arm-ftbfs.patch to fix FTBFS on armel and armhf.
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Tue, 17 Sep 2013 01:08:10 -0700
+
+0ad (0.0.14-2) unstable; urgency=low
+
+  * Tighten build dependency on libnvtt-dev to >= 2.0.8-1+dfsg-4 to ensure
+    that amd64 builds of 0ad do not FTBFS on certain hardware. See #713966
+    for details.
+  * Build 0ad for armel and armhf.
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Fri, 06 Sep 2013 23:00:24 -0700
+
+0ad (0.0.14-1) unstable; urgency=low
+
+  * New upstream release.
+    - Remove fix-bindir.patch, boost-libnames.patch; applied/fixed upstream.
+    - Refresh remaining patches.
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Thu, 05 Sep 2013 20:02:20 -0700
+
+0ad (0.0.13-2) unstable; urgency=low
+
+  * Add boost-libnames.patch to fix FTBFS with boost 1.53. (Closes: #709570)
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Fri, 24 May 2013 01:47:14 -0700
+
+0ad (0.0.13-1) unstable; urgency=low
+
+  * New upstream release.
+    - Refresh patches.
+  * Add icon to Debian menu entry. (Closes: #703254)
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Tue, 02 Apr 2013 19:40:54 -0700
+
+0ad (0.0.12-1) unstable; urgency=low
+
+  [ Vincent Cheng ]
+  * New upstream release.
+    - Remove debian/patches/fix-kfreebsd-ftbfs.patch; applied upstream.
+    - Refresh remaining patches.
+  * Add depends on 0ad-data-common.
+  * Add lintian overrides for:
+    - outdated-autotools-helper-file (caused by config.{guess,sub} in a
+      convenience copy of libenet, which we don't use).
+    - version-substvar-for-external-package (not a problem since 0ad and
+      0ad-data uploaded at same time, and no other mechanism provided by
+      dpkg-gencontrol for packages that want to depend on versioned packages
+      built from a separate source package).
+  * Update Standards version to 3.9.4, no changes required.
+  * Enable verbose build.
+
+  [ Ansgar Burchardt ]
+  * debian/control: Remove DM-Upload-Allowed.
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Tue, 12 Feb 2013 19:24:59 -0800
+
+0ad (0.0.11-1) unstable; urgency=low
+
+  * New upstream release. (Alpha 11 Kronos)
+    - Upstream disabled FAM support as of svn r12550, so remove build-depends
+      on libgamin-dev, and remove depends on gamin | fam. (Closes: #679087)
+    - Refresh existing patches.
+  * Add fallback to launcher script when /usr/games is not in $PATH.
+    (Closes: #679033)
+  * Restrict list of architectures to i386, amd64, and kfreebsd-{i386,amd64}.
+    (Closes: #683282)
+  * Update debian/watch file.
+  * Add DMUA field in debian/control with agreement of sponsor.
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Sat, 08 Sep 2012 02:43:25 -0700
+
+0ad (0~r11863-2) unstable; urgency=low
+
+  * Add stricter dependency on 0ad-data. (Closes: #673526)
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Sun, 20 May 2012 01:48:59 -0700
+
+0ad (0~r11863-1) unstable; urgency=low
+
+  * New upstream release.
+    - Refresh patches.
+  * Add debian/patches/enable-hardening-relro.patch to build using the
+    "read-only relocation" link flag, as suggested by lintian.
+  * Add build-depends on libxcursor-dev.
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Wed, 16 May 2012 23:50:12 -0700
+
+0ad (0~r11339-2) unstable; urgency=low
+
+  * Add debian/patches/fix-kfreebsd-ftbfs.patch to fix FTBFS on kfreebsd.
+  * Update debian/0ad.6.
+  * Add build-depends on dpkg-dev (>= 1.15.5) for xz compression support.
+  * Change build-depends on libjpeg8-dev to libjpeg-dev.
+  * Change build-depends on libpng12-dev to libpng-dev. (Closes: #668453)
+  * Add alternate build-depends on libcurl4-dev.
+  * Add alternate dependency on fam. (Closes: #668353)
+  * Override dh_auto_clean to clean source properly. (Closes: #668686)
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Thu, 26 Apr 2012 19:12:26 -0700
+
+0ad (0~r11339-1) unstable; urgency=low
+
+  * Initial release (Closes: #594800)
+
+ -- Vincent Cheng <Vincentc1208@gmail.com>  Thu, 29 Mar 2012 16:13:33 -0700
diff --git a/control b/control
new file mode 100644 (file)
index 0000000..1a1108b
--- /dev/null
+++ b/control
@@ -0,0 +1,63 @@
+Source: 0ad
+Section: games
+Priority: optional
+Maintainer: Debian Games Team <pkg-games-devel@lists.alioth.debian.org>
+Uploaders:
+ Vincent Cheng <vcheng@debian.org>,
+ Ludovic Rousseau <rousseau@debian.org>
+Build-Depends:
+ autoconf,
+ automake,
+ cargo,
+ cmake,
+ debhelper-compat (= 13),
+ dh-exec (>= 0.1),
+ dpkg-dev (>= 1.15.5),
+ libboost-dev (>= 1.57.0.1),
+ libboost-filesystem-dev (>= 1.57.0.1),
+ libcurl4-gnutls-dev (>= 7.32.0) | libcurl4-dev (>= 7.32.0),
+ libenet-dev (>= 1.3),
+ libfmt-dev (>= 4.0.0),
+ libfreetype-dev,
+ libgloox-dev (>= 1.0.10),
+ libicu-dev (>= 67.1-4~),
+ libminiupnpc-dev (>= 1.6),
+ libogg-dev,
+ libopenal-dev,
+ libpng-dev,
+ libsdl2-dev (>= 2.0.5),
+ libsodium-dev (>= 1.0.14),
+ libvorbis-dev,
+ libwxgtk3.0-gtk3-dev,
+ libxcursor-dev,
+ libxml2-dev,
+ llvm,
+ pkg-config,
+ python3,
+ rustc (>= 1.41),
+ tzdata,
+ zlib1g-dev (>= 1:1.2.3)
+Standards-Version: 4.6.1
+Homepage: https://play0ad.com/
+Vcs-Git: https://salsa.debian.org/games-team/0ad.git
+Vcs-Browser: https://salsa.debian.org/games-team/0ad
+Rules-Requires-Root: no
+
+Package: 0ad
+Architecture: amd64 arm64 armhf i386 kfreebsd-amd64 kfreebsd-i386
+Pre-Depends: dpkg (>= 1.15.6~)
+Depends:
+ 0ad-data (>= ${source:Upstream-Version}),
+ 0ad-data (<= ${source:Version}),
+ 0ad-data-common (>= ${source:Upstream-Version}),
+ 0ad-data-common (<= ${source:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: Real-time strategy game of ancient warfare
+ 0 A.D. (pronounced "zero ey-dee") is a free, open-source, cross-platform
+ real-time strategy (RTS) game of ancient warfare. In short, it is a
+ historically-based war/economy game that allows players to relive or rewrite
+ the history of Western civilizations, focusing on the years between 500 B.C.
+ and 500 A.D. The project is highly ambitious, involving state-of-the-art 3D
+ graphics, detailed artwork, sound, and a flexible and powerful custom-built
+ game engine.
diff --git a/copyright b/copyright
new file mode 100644 (file)
index 0000000..a613371
--- /dev/null
+++ b/copyright
@@ -0,0 +1,540 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: 0ad
+Source: https://releases.wildfiregames.com/
+
+Files: *
+Copyright: 2000-2021 Wildfire Games
+License: GPL-2.0+
+
+
+Files: debian/*
+Copyright: 2010-2012 Rico Tzschichholz <ricotz@ubuntu.com>
+           2010      Bertrand Marc <BeberKing@gmail.com>
+           2011-2020 Vincent Cheng <vcheng@debian.org>
+           2011-2012 Ansgar Burchardt <ansgar@debian.org>
+           2017-2022 Ludovic Rousseau <rousseau@debian.org>
+           2019      Bruno Kleinert <fuddl@debian.org>
+           2021      Phil Morrell <debian@emorrp1.name>
+           2021      Pino Toscano <pino@debian.org>
+           2021      David W. Kennedy <dave_k@reasoned.us>
+License: GPL-2.0+
+
+Files: debian/0ad.6
+Copyright: 2010 Fabio Pedretti <fabio.ped@libero.it>
+License: GPL-2.0+
+
+Files: source/lib/*
+Copyright: 2010 Wildfire Games
+License: Expat
+
+Files: source/third_party/*
+Copyright: 2004-2011 Sergey Lyubka
+License: Expat
+
+Files: source/tools/fontbuilder2/Packer.py
+Copyright: 2002-2009 Nuclex Development Labs
+License: CPL
+
+Files: source/tools/templatesanalyzer/tablefilter/*
+Copyright: 2014-2016 Max Guglielmi
+License: Expat
+
+Files: build/premake/*
+Copyright: 2002-2009 Jason Perkins and the Premake project
+License: BSD-3-clause
+
+Files: libraries/source/cxxtest-4.4/*
+Copyright: 2004 Erez Volk
+           2004 Jared Grubb
+           2004 Kevin Fitch
+License: LGPL-2.1+
+
+Files: libraries/source/fcollada/*
+Copyright: 2005-2007 Feeling Software Inc.
+           2005-2007 Sony Computer Entertainment America
+License: Expat
+
+Files: libraries/source/fcollada/include/FCDocument/*
+       libraries/source/fcollada/include/FUtils/*
+       libraries/source/fcollada/src/FCollada/FCDocument/*
+       libraries/source/fcollada/src/FCollada/FUtils/*
+       libraries/source/fcollada/src/FColladaPlugins/FArchiveXML/*
+Copyright: 2005-2006 Autodesk Media Entertainment
+           2005-2007 Feeling Software Inc.
+           2005-2007 Sony Computer Entertainment America
+License: Expat
+
+Files: libraries/source/nvtt/*
+Copyright: 2006 Simon Brown <si@sjbrown.co.uk>
+           2007-2009 NVIDIA Corporation
+           2006, 2008-2016 Ignacio Castano <castano@gmail.com>
+License: Expat
+
+Files: libraries/source/nvtt/src/src/nvtt/squish/*
+Copyright: 2006 Simon Brown <si@sjbrown.co.uk>
+           2006 Ignacio Castano <castano@gmail.com>
+           2016 Raptor Engineering, LLC
+License: Expat
+
+Files: libraries/source/nvtt/src/src/nvcore/Array.h
+       libraries/source/nvtt/src/src/nvcore/Array.inl
+       libraries/source/nvtt/src/src/nvcore/Debug.cpp
+       libraries/source/nvtt/src/src/nvcore/Debug.h
+       libraries/source/nvtt/src/src/nvcore/DefsGnucDarwin.h
+       libraries/source/nvtt/src/src/nvcore/DefsGnucLinux.h
+       libraries/source/nvtt/src/src/nvcore/DefsGnucWin32.h
+       libraries/source/nvtt/src/src/nvcore/DefsVcWin32.h
+       libraries/source/nvtt/src/src/nvcore/FileSystem.cpp
+       libraries/source/nvtt/src/src/nvcore/FileSystem.h
+       libraries/source/nvtt/src/src/nvcore/ForEach.h
+       libraries/source/nvtt/src/src/nvcore/Hash.h
+       libraries/source/nvtt/src/src/nvcore/Memory.cpp
+       libraries/source/nvtt/src/src/nvcore/Memory.h
+       libraries/source/nvtt/src/src/nvcore/nvcore.h
+       libraries/source/nvtt/src/src/nvcore/Ptr.h
+       libraries/source/nvtt/src/src/nvcore/RefCounted.h
+       libraries/source/nvtt/src/src/nvcore/StdStream.h
+       libraries/source/nvtt/src/src/nvcore/Stream.h
+       libraries/source/nvtt/src/src/nvcore/StrLib.cpp
+       libraries/source/nvtt/src/src/nvcore/StrLib.h
+       libraries/source/nvtt/src/src/nvcore/TextWriter.cpp
+       libraries/source/nvtt/src/src/nvcore/TextWriter.h
+       libraries/source/nvtt/src/src/nvcore/Timer.cpp
+       libraries/source/nvtt/src/src/nvcore/Timer.h
+       libraries/source/nvtt/src/src/nvcore/Utils.h
+       libraries/source/nvtt/src/src/nvimage/BlockDXT.cpp
+       libraries/source/nvtt/src/src/nvimage/BlockDXT.h
+       libraries/source/nvtt/src/src/nvimage/CMakeLists.txt
+       libraries/source/nvtt/src/src/nvimage/ColorBlock.cpp
+       libraries/source/nvtt/src/src/nvimage/ColorBlock.h
+       libraries/source/nvtt/src/src/nvimage/ColorSpace.cpp
+       libraries/source/nvtt/src/src/nvimage/ColorSpace.h
+       libraries/source/nvtt/src/src/nvimage/DirectDrawSurface.cpp
+       libraries/source/nvtt/src/src/nvimage/DirectDrawSurface.h
+       libraries/source/nvtt/src/src/nvimage/ErrorMetric.cpp
+       libraries/source/nvtt/src/src/nvimage/ErrorMetric.h
+       libraries/source/nvtt/src/src/nvimage/Filter.cpp
+       libraries/source/nvtt/src/src/nvimage/Filter.h
+       libraries/source/nvtt/src/src/nvimage/FloatImage.cpp
+       libraries/source/nvtt/src/src/nvimage/FloatImage.h
+       libraries/source/nvtt/src/src/nvimage/Image.cpp
+       libraries/source/nvtt/src/src/nvimage/Image.h
+       libraries/source/nvtt/src/src/nvimage/ImageIO.cpp
+       libraries/source/nvtt/src/src/nvimage/ImageIO.h
+       libraries/source/nvtt/src/src/nvimage/KtxFile.cpp
+       libraries/source/nvtt/src/src/nvimage/KtxFile.h
+       libraries/source/nvtt/src/src/nvimage/NormalMap.cpp
+       libraries/source/nvtt/src/src/nvimage/NormalMap.h
+       libraries/source/nvtt/src/src/nvimage/nvimage.h
+       libraries/source/nvtt/src/src/nvimage/PixelFormat.h
+       libraries/source/nvtt/src/src/nvimage/PsdFile.h
+       libraries/source/nvtt/src/src/nvimage/Quantize.cpp
+       libraries/source/nvtt/src/src/nvimage/Quantize.h
+       libraries/source/nvtt/src/src/nvimage/TgaFile.h
+       libraries/source/nvtt/src/src/nvimage/ValveTextureFormat.h
+       libraries/source/nvtt/src/src/nvmath/Box.cpp
+       libraries/source/nvtt/src/src/nvmath/Box.h
+       libraries/source/nvtt/src/src/nvmath/Box.inl
+       libraries/source/nvtt/src/src/nvmath/Color.cpp
+       libraries/source/nvtt/src/src/nvmath/Color.h
+       libraries/source/nvtt/src/src/nvmath/Color.inl
+       libraries/source/nvtt/src/src/nvmath/Fitting.cpp
+       libraries/source/nvtt/src/src/nvmath/Fitting.h
+       libraries/source/nvtt/src/src/nvmath/ftoi.h
+       libraries/source/nvtt/src/src/nvmath/Matrix.h
+       libraries/source/nvtt/src/src/nvmath/Matrix.inl
+       libraries/source/nvtt/src/src/nvmath/nvmath.h
+       libraries/source/nvtt/src/src/nvmath/PackedFloat.cpp
+       libraries/source/nvtt/src/src/nvmath/PackedFloat.h
+       libraries/source/nvtt/src/src/nvmath/Plane.cpp
+       libraries/source/nvtt/src/src/nvmath/Plane.h
+       libraries/source/nvtt/src/src/nvmath/Plane.inl
+       libraries/source/nvtt/src/src/nvmath/SimdVector.h
+       libraries/source/nvtt/src/src/nvmath/SphericalHarmonic.cpp
+       libraries/source/nvtt/src/src/nvmath/SphericalHarmonic.h
+       libraries/source/nvtt/src/src/nvmath/Vector.cpp
+       libraries/source/nvtt/src/src/nvmath/Vector.h
+       libraries/source/nvtt/src/src/nvmath/Vector.inl
+       libraries/source/nvtt/src/src/nvthread/Atomic.h
+       libraries/source/nvtt/src/src/nvthread/Event.cpp
+       libraries/source/nvtt/src/src/nvthread/Event.h
+       libraries/source/nvtt/src/src/nvthread/Mutex.cpp
+       libraries/source/nvtt/src/src/nvthread/Mutex.h
+       libraries/source/nvtt/src/src/nvthread/nvthread.cpp
+       libraries/source/nvtt/src/src/nvthread/nvthread.h
+       libraries/source/nvtt/src/src/nvthread/ParallelFor.cpp
+       libraries/source/nvtt/src/src/nvthread/ParallelFor.h
+       libraries/source/nvtt/src/src/nvthread/Thread.cpp
+       libraries/source/nvtt/src/src/nvthread/Thread.h
+       libraries/source/nvtt/src/src/nvthread/ThreadPool.cpp
+       libraries/source/nvtt/src/src/nvthread/ThreadPool.h
+       libraries/source/nvtt/src/src/nvthread/Win32.h
+Copyright: Ignacio Castano <castanyo@yahoo.es>
+License: public-domain
+ The license is not specified beyond public-domain.
+ The line from the source code is the following:
+ "This code is in the public domain -- castanyo@yahoo.es"
+
+Files: libraries/source/nvtt/src/src/nvmath/Gamma.*
+Copyright: 2017 Ken Cooke <ken@highfidelity.io>
+License: Expat
+
+Files: libraries/source/nvtt/src/src/nvmath/Half.*
+Copyright: 2006 Mike Acton <macton@gmail.com>
+License: Expat
+
+Files: libraries/source/nvtt/src/src/nvmath/SimdVector_SSE.h
+Copyright: 2006 Simon Brown <si@sjbrown.co.uk>
+License: Expat
+
+Files: libraries/source/nvtt/src/src/nvmath/SimdVector_VE.h
+Copyright: 2006 Simon Brown <si@sjbrown.co.uk>
+           2016 Raptor Engineering
+License: Expat
+
+Files: libraries/source/nvtt/src/src/nvmath/Matrix.cpp
+Copyright: Paul Heckbert <ph@cs.cmu.edu>
+           1999-2004 Michael Garland
+           Ignacio Castano <castanyo@yahoo.es>
+License: Expat
+
+Files: libraries/source/nvtt/src/extern/poshlib/posh.*
+Copyright: 2002-2006 Brian Hook
+License: BSD-3-clause
+
+Files: libraries/source/spidermonkey/*
+Copyright: 1998-2020 Mozilla Corporation
+License: MPL-2.0 and GPL-2.0 and LGPL-2.1
+
+License: BSD-3-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+     * Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+     * Neither the name of the <organization> nor the
+       names of its contributors may be used to endorse or promote products
+       derived from this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+License: CPL
+ THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC
+ LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
+ CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+ .
+ 1. DEFINITIONS
+ .
+ "Contribution" means:
+ .
+ a) in the case of the initial Contributor, the initial code and
+ documentation distributed under this Agreement, and
+ .
+ b) in the case of each subsequent Contributor:
+ .
+ i) changes to the Program, and
+ .
+ ii) additions to the Program;
+ .
+ where such changes and/or additions to the Program originate from and are
+ distributed by that particular Contributor. A Contribution 'originates' from a
+ Contributor if it was added to the Program by such Contributor itself or anyone
+ acting on such Contributor's behalf. Contributions do not include additions to
+ the Program which: (i) are separate modules of software distributed in
+ conjunction with the Program under their own license agreement, and (ii) are not
+ derivative works of the Program.
+ .
+ "Contributor" means any person or entity that distributes the Program.
+ .
+ "Licensed Patents " mean patent claims licensable by a Contributor which are
+ necessarily infringed by the use or sale of its Contribution alone or when
+ combined with the Program.
+ .
+ "Program" means the Contributions distributed in accordance with this Agreement.
+ .
+ "Recipient" means anyone who receives the Program under this Agreement,
+ including all Contributors.
+ .
+ 2. GRANT OF RIGHTS
+ .
+ a) Subject to the terms of this Agreement, each Contributor hereby grants
+ Recipient a non-exclusive, worldwide, royalty-free copyright license to
+ reproduce, prepare derivative works of, publicly display, publicly perform,
+ distribute and sublicense the Contribution of such Contributor, if any, and such
+ derivative works, in source code and object code form.
+ .
+ b) Subject to the terms of this Agreement, each Contributor hereby grants
+ Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed
+ Patents to make, use, sell, offer to sell, import and otherwise transfer the
+ Contribution of such Contributor, if any, in source code and object code form.
+ This patent license shall apply to the combination of the Contribution and the
+ Program if, at the time the Contribution is added by the Contributor, such
+ addition of the Contribution causes such combination to be covered by the
+ Licensed Patents. The patent license shall not apply to any other combinations
+ which include the Contribution. No hardware per se is licensed hereunder.
+ .
+ c) Recipient understands that although each Contributor grants the licenses
+ to its Contributions set forth herein, no assurances are provided by any
+ Contributor that the Program does not infringe the patent or other intellectual
+ property rights of any other entity. Each Contributor disclaims any liability to
+ Recipient for claims brought by any other entity based on infringement of
+ intellectual property rights or otherwise. As a condition to exercising the
+ rights and licenses granted hereunder, each Recipient hereby assumes sole
+ responsibility to secure any other intellectual property rights needed, if any.
+ For example, if a third party patent license is required to allow Recipient to
+ distribute the Program, it is Recipient's responsibility to acquire that license
+ before distributing the Program.
+ .
+ d) Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright license set
+ forth in this Agreement.
+ .
+ 3. REQUIREMENTS
+ .
+ A Contributor may choose to distribute the Program in object code form under its
+ own license agreement, provided that:
+ .
+ a) it complies with the terms and conditions of this Agreement; and
+ .
+ b) its license agreement:
+ .
+ i) effectively disclaims on behalf of all Contributors all warranties and
+ conditions, express and implied, including warranties or conditions of title and
+ non-infringement, and implied warranties or conditions of merchantability and
+ fitness for a particular purpose;
+ .
+ ii) effectively excludes on behalf of all Contributors all liability for
+ damages, including direct, indirect, special, incidental and consequential
+ damages, such as lost profits;
+ .
+ iii) states that any provisions which differ from this Agreement are offered
+ by that Contributor alone and not by any other party; and
+ .
+ iv) states that source code for the Program is available from such
+ Contributor, and informs licensees how to obtain it in a reasonable manner on or
+ through a medium customarily used for software exchange.
+ .
+ When the Program is made available in source code form:
+ .
+ a) it must be made available under this Agreement; and
+ .
+ b) a copy of this Agreement must be included with each copy of the Program.
+ .
+ Contributors may not remove or alter any copyright notices contained within the
+ Program.
+ .
+ Each Contributor must identify itself as the originator of its Contribution, if
+ any, in a manner that reasonably allows subsequent Recipients to identify the
+ originator of the Contribution.
+ .
+ 4. COMMERCIAL DISTRIBUTION
+ .
+ Commercial distributors of software may accept certain responsibilities with
+ respect to end users, business partners and the like. While this license is
+ intended to facilitate the commercial use of the Program, the Contributor who
+ includes the Program in a commercial product offering should do so in a manner
+ which does not create potential liability for other Contributors. Therefore, if
+ a Contributor includes the Program in a commercial product offering, such
+ Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
+ every other Contributor ("Indemnified Contributor") against any losses, damages
+ and costs (collectively "Losses") arising from claims, lawsuits and other legal
+ actions brought by a third party against the Indemnified Contributor to the
+ extent caused by the acts or omissions of such Commercial Contributor in
+ connection with its distribution of the Program in a commercial product
+ offering. The obligations in this section do not apply to any claims or Losses
+ relating to any actual or alleged intellectual property infringement. In order
+ to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
+ Contributor in writing of such claim, and b) allow the Commercial Contributor to
+ control, and cooperate with the Commercial Contributor in, the defense and any
+ related settlement negotiations. The Indemnified Contributor may participate in
+ any such claim at its own expense.
+ .
+ For example, a Contributor might include the Program in a commercial product
+ offering, Product X. That Contributor is then a Commercial Contributor. If that
+ Commercial Contributor then makes performance claims, or offers warranties
+ related to Product X, those performance claims and warranties are such
+ Commercial Contributor's responsibility alone. Under this section, the
+ Commercial Contributor would have to defend claims against the other
+ Contributors related to those performance claims and warranties, and if a court
+ requires any other Contributor to pay any damages as a result, the Commercial
+ Contributor must pay those damages.
+ .
+ 5. NO WARRANTY
+ .
+ EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+ IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
+ NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
+ Recipient is solely responsible for determining the appropriateness of using and
+ distributing the Program and assumes all risks associated with its exercise of
+ rights under this Agreement, including but not limited to the risks and costs of
+ program errors, compliance with applicable laws, damage to or loss of data,
+ programs or equipment, and unavailability or interruption of operations.
+ .
+ 6. DISCLAIMER OF LIABILITY
+ .
+ EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
+ CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+ PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
+ GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ .
+ 7. GENERAL
+ .
+ If any provision of this Agreement is invalid or unenforceable under applicable
+ law, it shall not affect the validity or enforceability of the remainder of the
+ terms of this Agreement, and without further action by the parties hereto, such
+ provision shall be reformed to the minimum extent necessary to make such
+ provision valid and enforceable.
+ .
+ If Recipient institutes patent litigation against a Contributor with respect to
+ a patent applicable to software (including a cross-claim or counterclaim in a
+ lawsuit), then any patent licenses granted by that Contributor to such Recipient
+ under this Agreement shall terminate as of the date such litigation is filed. In
+ addition, if Recipient institutes patent litigation against any entity
+ (including a cross-claim or counterclaim in a lawsuit) alleging that the Program
+ itself (excluding combinations of the Program with other software or hardware)
+ infringes such Recipient's patent(s), then such Recipient's rights granted under
+ Section 2(b) shall terminate as of the date such litigation is filed.
+ .
+ All Recipient's rights under this Agreement shall terminate if it fails to
+ comply with any of the material terms or conditions of this Agreement and does
+ not cure such failure in a reasonable period of time after becoming aware of
+ such noncompliance. If all Recipient's rights under this Agreement terminate,
+ Recipient agrees to cease use and distribution of the Program as soon as
+ reasonably practicable. However, Recipient's obligations under this Agreement
+ and any licenses granted by Recipient relating to the Program shall continue and
+ survive.
+ .
+ Everyone is permitted to copy and distribute copies of this Agreement, but in
+ order to avoid inconsistency the Agreement is copyrighted and may only be
+ modified in the following manner. The Agreement Steward reserves the right to
+ publish new versions (including revisions) of this Agreement from time to time.
+ No one other than the Agreement Steward has the right to modify this Agreement.
+ IBM is the initial Agreement Steward. IBM may assign the responsibility to serve
+ as the Agreement Steward to a suitable separate entity. Each new version of the
+ Agreement will be given a distinguishing version number. The Program (including
+ Contributions) may always be distributed subject to the version of the Agreement
+ under which it was received. In addition, after a new version of the Agreement
+ is published, Contributor may elect to distribute the Program (including its
+ Contributions) under the new version. Except as expressly stated in Sections
+ 2(a) and 2(b) above, Recipient receives no rights or licenses to the
+ intellectual property of any Contributor under this Agreement, whether
+ expressly, by implication, estoppel or otherwise. All rights in the Program not
+ expressly granted under this Agreement are reserved.
+ .
+ This Agreement is governed by the laws of the State of New York and the
+ intellectual property laws of the United States of America. No party to this
+ Agreement will bring a legal action under this Agreement more than one year
+ after the cause of action arose. Each party waives its rights to a jury trial in
+ any resulting litigation.
+
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE
+
+License: GPL-2.0
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License, version 2 of
+ the License, as published by the Free Software Foundation.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+
+License: GPL-2.0+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+
+License: LGPL-2.1
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License, version 2.1 of the License, as published by the
+ Free Software Foundation.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/
+ LGPL-2.1".
+
+License: LGPL-2.1+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/
+ LGPL-2.1".
+
+License: MPL-2.0
+   This Source Code Form is subject to the terms of the Mozilla Public
+   License, v. 2.0. If a copy of the MPL was not distributed with this
+   file, You can obtain one at http://mozilla.org/MPL/2.0/.
+   .
+   On Debian systems, the complete text of the Mozilla Public License,
+   version 2 can be found in "/usr/share/common-licenses/MPL-2.0".
diff --git a/docs b/docs
new file mode 100644 (file)
index 0000000..71dfd5b
--- /dev/null
+++ b/docs
@@ -0,0 +1 @@
+README.txt
diff --git a/install b/install
new file mode 100755 (executable)
index 0000000..6fa8d8a
--- /dev/null
+++ b/install
@@ -0,0 +1,16 @@
+#!/usr/bin/dh-exec --with=install
+binaries/data/l10n                              usr/share/games/0ad/
+binaries/system/libAtlasUI.so                   usr/lib/games/0ad/
+binaries/system/libCollada.so                   usr/lib/games/0ad/
+binaries/system/libmozjs78-ps-release.so        usr/lib/games/0ad/
+binaries/system/ActorEditor                     usr/lib/games/0ad/
+binaries/system/libnvtt.so                      usr/lib/games/0ad/
+binaries/system/libnvcore.so                    usr/lib/games/0ad/
+binaries/system/libnvimage.so                   usr/lib/games/0ad/
+binaries/system/libnvmath.so                    usr/lib/games/0ad/
+binaries/system/pyrogenesis                     usr/games/
+build/resources/0ad.appdata.xml                 usr/share/metainfo/
+build/resources/0ad.desktop                     usr/share/applications/
+build/resources/0ad.png                         usr/share/icons/hicolor/128x128/apps/
+usr/games/0ad
+binaries/system/readme.txt =>                   usr/share/doc/0ad/README.command-line.txt
diff --git a/manpages b/manpages
new file mode 100644 (file)
index 0000000..4ff5397
--- /dev/null
+++ b/manpages
@@ -0,0 +1,2 @@
+debian/0ad.6
+debian/pyrogenesis.6
diff --git a/missing-sources/README.txt b/missing-sources/README.txt
new file mode 100644 (file)
index 0000000..427fdd4
--- /dev/null
@@ -0,0 +1,21 @@
+This directory provides the non-minified versions of the embedded JQuery files
+in the orig source tarball, in order to comply with Debian Policy. (#735349)
+
+The minified javascript files for which this directory provides the
+non-minified versions are as follows. Each of the lines below indicate the
+minified file, followed by the non-minified source file(s) present in this
+directory.
+
+source/tools/replayprofile/jquery.flot.js:
+    jquery.colorhelpers.js
+source/tools/replayprofile/jquery.flot.navigate.js:
+    jquery.event.drag.js
+    jquery.mousewheel.js
+
+source/tools/templatesanalyzer/tablefilter/tablefilter.js:
+source/tools/templatesanalyzer/tablefilter/tf-1.js:
+    tablefilter/*.js
+
+(Tablefilter 0.0.11: https://github.com/koalyptus/TableFilter/tree/4f2316a4b57021a41edd10d3b98e7f8159642392)
+
+
diff --git a/missing-sources/jquery.colorhelpers.js b/missing-sources/jquery.colorhelpers.js
new file mode 100644 (file)
index 0000000..fa44961
--- /dev/null
@@ -0,0 +1,174 @@
+/* Plugin for jQuery for working with colors.
+ * 
+ * Version 1.0.
+ * 
+ * Inspiration from jQuery color animation plugin by John Resig.
+ *
+ * Released under the MIT license by Ole Laursen, October 2009.
+ *
+ * Examples:
+ *
+ *   $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
+ *   var c = $.color.extract($("#mydiv"), 'background-color');
+ *   console.log(c.r, c.g, c.b, c.a);
+ *   $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
+ *
+ * Note that .scale() and .add() work in-place instead of returning
+ * new objects.
+ */ 
+
+(function() {
+    jQuery.color = {};
+
+    // construct color object with some convenient chainable helpers
+    jQuery.color.make = function (r, g, b, a) {
+        var o = {};
+        o.r = r || 0;
+        o.g = g || 0;
+        o.b = b || 0;
+        o.a = a != null ? a : 1;
+
+        o.add = function (c, d) {
+            for (var i = 0; i < c.length; ++i)
+                o[c.charAt(i)] += d;
+            return o.normalize();
+        };
+        
+        o.scale = function (c, f) {
+            for (var i = 0; i < c.length; ++i)
+                o[c.charAt(i)] *= f;
+            return o.normalize();
+        };
+        
+        o.toString = function () {
+            if (o.a >= 1.0) {
+                return "rgb("+[o.r, o.g, o.b].join(",")+")";
+            } else {
+                return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")";
+            }
+        };
+
+        o.normalize = function () {
+            function clamp(min, value, max) {
+                return value < min ? min: (value > max ? max: value);
+            }
+            
+            o.r = clamp(0, parseInt(o.r), 255);
+            o.g = clamp(0, parseInt(o.g), 255);
+            o.b = clamp(0, parseInt(o.b), 255);
+            o.a = clamp(0, o.a, 1);
+            return o;
+        };
+
+        o.clone = function () {
+            return jQuery.color.make(o.r, o.b, o.g, o.a);
+        };
+
+        return o.normalize();
+    }
+
+    // extract CSS color property from element, going up in the DOM
+    // if it's "transparent"
+    jQuery.color.extract = function (elem, css) {
+        var c;
+        do {
+            c = elem.css(css).toLowerCase();
+            // keep going until we find an element that has color, or
+            // we hit the body
+            if (c != '' && c != 'transparent')
+                break;
+            elem = elem.parent();
+        } while (!jQuery.nodeName(elem.get(0), "body"));
+
+        // catch Safari's way of signalling transparent
+        if (c == "rgba(0, 0, 0, 0)")
+            c = "transparent";
+        
+        return jQuery.color.parse(c);
+    }
+    
+    // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"),
+    // returns color object
+    jQuery.color.parse = function (str) {
+        var res, m = jQuery.color.make;
+
+        // Look for rgb(num,num,num)
+        if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
+            return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10));
+        
+        // Look for rgba(num,num,num,num)
+        if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
+            return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4]));
+            
+        // Look for rgb(num%,num%,num%)
+        if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
+            return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55);
+
+        // Look for rgba(num%,num%,num%,num)
+        if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
+            return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4]));
+        
+        // Look for #a0b1c2
+        if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
+            return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16));
+
+        // Look for #fff
+        if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
+            return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16));
+
+        // Otherwise, we're most likely dealing with a named color
+        var name = jQuery.trim(str).toLowerCase();
+        if (name == "transparent")
+            return m(255, 255, 255, 0);
+        else {
+            res = lookupColors[name];
+            return m(res[0], res[1], res[2]);
+        }
+    }
+    
+    var lookupColors = {
+        aqua:[0,255,255],
+        azure:[240,255,255],
+        beige:[245,245,220],
+        black:[0,0,0],
+        blue:[0,0,255],
+        brown:[165,42,42],
+        cyan:[0,255,255],
+        darkblue:[0,0,139],
+        darkcyan:[0,139,139],
+        darkgrey:[169,169,169],
+        darkgreen:[0,100,0],
+        darkkhaki:[189,183,107],
+        darkmagenta:[139,0,139],
+        darkolivegreen:[85,107,47],
+        darkorange:[255,140,0],
+        darkorchid:[153,50,204],
+        darkred:[139,0,0],
+        darksalmon:[233,150,122],
+        darkviolet:[148,0,211],
+        fuchsia:[255,0,255],
+        gold:[255,215,0],
+        green:[0,128,0],
+        indigo:[75,0,130],
+        khaki:[240,230,140],
+        lightblue:[173,216,230],
+        lightcyan:[224,255,255],
+        lightgreen:[144,238,144],
+        lightgrey:[211,211,211],
+        lightpink:[255,182,193],
+        lightyellow:[255,255,224],
+        lime:[0,255,0],
+        magenta:[255,0,255],
+        maroon:[128,0,0],
+        navy:[0,0,128],
+        olive:[128,128,0],
+        orange:[255,165,0],
+        pink:[255,192,203],
+        purple:[128,0,128],
+        violet:[128,0,128],
+        red:[255,0,0],
+        silver:[192,192,192],
+        white:[255,255,255],
+        yellow:[255,255,0]
+    };    
+})();
diff --git a/missing-sources/jquery.event.drag.js b/missing-sources/jquery.event.drag.js
new file mode 100644 (file)
index 0000000..ad66b10
--- /dev/null
@@ -0,0 +1,137 @@
+/*! \r
+jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)  \r
+Liscensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt\r
+*/\r
+;(function($){ // secure $ jQuery alias\r
+/*******************************************************************************************/\r
+// Created: 2008-06-04 | Updated: 2009-03-24\r
+/*******************************************************************************************/\r
+// Events: drag, dragstart, dragend\r
+/*******************************************************************************************/\r
+\r
+// jquery method\r
+$.fn.drag = function( fn1, fn2, fn3 ){\r
+       if ( fn2 ) this.bind('dragstart', fn1 ); // 2+ args\r
+       if ( fn3 ) this.bind('dragend', fn3 ); // 3 args\r
+       return !fn1 ? this.trigger('drag') // 0 args\r
+               : this.bind('drag', fn2 ? fn2 : fn1 ); // 1+ args\r
+       };\r
+\r
+// local refs\r
+var $event = $.event, $special = $event.special,\r
+\r
+// special event configuration\r
+drag = $special.drag = {\r
+       not: ':input', // don't begin to drag on event.targets that match this selector\r
+       distance: 0, // distance dragged before dragstart\r
+       which: 1, // mouse button pressed to start drag sequence\r
+       dragging: false, // hold the active target element\r
+       setup: function( data ){\r
+               data = $.extend({ \r
+                       distance: drag.distance, \r
+                       which: drag.which, \r
+                       not: drag.not\r
+                       }, data || {});\r
+               data.distance = squared( data.distance ); //  x² + y² = distance²\r
+               $event.add( this, "mousedown", handler, data );\r
+               if ( this.attachEvent ) this.attachEvent("ondragstart", dontStart ); // prevent image dragging in IE...\r
+               },\r
+       teardown: function(){\r
+               $event.remove( this, "mousedown", handler );\r
+               if ( this === drag.dragging ) drag.dragging = drag.proxy = false; // deactivate element\r
+               selectable( this, true ); // enable text selection\r
+               if ( this.detachEvent ) this.detachEvent("ondragstart", dontStart ); // prevent image dragging in IE...\r
+               }\r
+       };\r
+       \r
+// prevent normal event binding...\r
+$special.dragstart = $special.dragend = { setup:function(){}, teardown:function(){} };\r
+\r
+// handle drag-releatd DOM events\r
+function handler ( event ){ \r
+       var elem = this, returned, data = event.data || {};\r
+       // mousemove or mouseup\r
+       if ( data.elem ){ \r
+               // update event properties...\r
+               elem = event.dragTarget = data.elem; // drag source element\r
+               event.dragProxy = drag.proxy || elem; // proxy element or source\r
+               event.cursorOffsetX = data.pageX - data.left; // mousedown offset\r
+               event.cursorOffsetY = data.pageY - data.top; // mousedown offset\r
+               event.offsetX = event.pageX - event.cursorOffsetX; // element offset\r
+               event.offsetY = event.pageY - event.cursorOffsetY; // element offset\r
+               }\r
+       // mousedown, check some initial props to avoid the switch statement\r
+       else if ( drag.dragging || ( data.which>0 && event.which!=data.which ) || \r
+               $( event.target ).is( data.not ) ) return;\r
+       // handle various events\r
+       switch ( event.type ){\r
+               // mousedown, left click, event.target is not restricted, init dragging\r
+               case 'mousedown':\r
+                       $.extend( data, $( elem ).offset(), { \r
+                               elem: elem, target: event.target,\r
+                               pageX: event.pageX, pageY: event.pageY\r
+                               }); // store some initial attributes\r
+                       $event.add( document, "mousemove mouseup", handler, data );\r
+                       selectable( elem, false ); // disable text selection\r
+                       drag.dragging = null; // pending state\r
+                       return false; // prevents text selection in safari \r
+               // mousemove, check distance, start dragging\r
+               case !drag.dragging && 'mousemove': \r
+                       if ( squared( event.pageX-data.pageX ) \r
+                               + squared( event.pageY-data.pageY ) //  x² + y² = distance²\r
+                               < data.distance ) break; // distance tolerance not reached\r
+                       event.target = data.target; // force target from "mousedown" event (fix distance issue)\r
+                       returned = hijack( event, "dragstart", elem ); // trigger "dragstart", return proxy element\r
+                       if ( returned !== false ){ // "dragstart" not rejected\r
+                               drag.dragging = elem; // activate element\r
+                               drag.proxy = event.dragProxy = $( returned || elem )[0]; // set proxy\r
+                               }\r
+               // mousemove, dragging\r
+               case 'mousemove': \r
+                       if ( drag.dragging ){\r
+                               returned = hijack( event, "drag", elem ); // trigger "drag"             \r
+                               if ( $special.drop ){ // manage drop events\r
+                                       $special.drop.allowed = ( returned !== false ); // prevent drop\r
+                                       $special.drop.handler( event ); // "dropstart", "dropend"\r
+                                       }\r
+                               if ( returned !== false ) break; // "drag" not rejected, stop           \r
+                               event.type = "mouseup"; // helps "drop" handler behave\r
+                               }\r
+               // mouseup, stop dragging\r
+               case 'mouseup': \r
+                       $event.remove( document, "mousemove mouseup", handler ); // remove page events\r
+                       if ( drag.dragging ){\r
+                               if ( $special.drop ) $special.drop.handler( event ); // "drop"\r
+                               hijack( event, "dragend", elem ); // trigger "dragend"  \r
+                               }\r
+                       selectable( elem, true ); // enable text selection\r
+                       drag.dragging = drag.proxy = data.elem = false; // deactivate element\r
+                       break;\r
+               } \r
+       return true;\r
+       };\r
+\r
+// set event type to custom value, and handle it\r
+function hijack ( event, type, elem ){\r
+       event.type = type; // force the event type\r
+       var result = $.event.handle.call( elem, event );\r
+       return result===false ? false : result || event.result;\r
+       };\r
+       \r
+// return the value squared    \r
+function squared ( value ){ return Math.pow( value, 2 ); };\r
+\r
+// suppress default dragstart IE events...\r
+function dontStart(){ return ( drag.dragging === false ); };   \r
+\r
+// toggles text selection attributes   \r
+function selectable ( elem, bool ){ \r
+       if ( !elem ) return; // maybe element was removed ? \r
+       elem.unselectable = bool ? "off" : "on"; // IE\r
+       elem.onselectstart = function(){ return bool; }; // IE\r
+       //if ( document.selection && document.selection.empty ) document.selection.empty(); // IE\r
+       if ( elem.style ) elem.style.MozUserSelect = bool ? "" : "none"; // FF\r
+       };      \r
+       \r
+/*******************************************************************************************/\r
+})( jQuery ); // confine scope
\ No newline at end of file
diff --git a/missing-sources/jquery.mousewheel.js b/missing-sources/jquery.mousewheel.js
new file mode 100644 (file)
index 0000000..528defe
--- /dev/null
@@ -0,0 +1,79 @@
+/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
+ * Licensed under the MIT License (LICENSE.txt).
+ *
+ * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
+ * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
+ * Thanks to: Seamus Leahy for adding deltaX and deltaY
+ *
+ * Version: 3.0.3
+ * 
+ * Requires: 1.2.2+
+ */
+
+(function($) {
+
+var types = ['DOMMouseScroll', 'mousewheel'];
+
+$.event.special.mousewheel = {
+    setup: function() {
+        if ( this.addEventListener ) {
+            for ( var i=types.length; i; ) {
+                this.addEventListener( types[--i], handler, false );
+            }
+        } else {
+            this.onmousewheel = handler;
+        }
+    },
+    
+    teardown: function() {
+        if ( this.removeEventListener ) {
+            for ( var i=types.length; i; ) {
+                this.removeEventListener( types[--i], handler, false );
+            }
+        } else {
+            this.onmousewheel = null;
+        }
+    }
+};
+
+$.fn.extend({
+    mousewheel: function(fn) {
+        return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
+    },
+    
+    unmousewheel: function(fn) {
+        return this.unbind("mousewheel", fn);
+    }
+});
+
+
+function handler(event) {
+    var orgEvent = event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
+    
+    event = $.event.fix(event || window.event);
+    event.type = "mousewheel";
+    
+    // Old school scrollwheel delta
+    if ( event.wheelDelta ) { delta = event.wheelDelta/120; }
+    if ( event.detail     ) { delta = -event.detail/3; }
+    
+    // New school multidimensional scroll (touchpads) deltas
+    deltaY = delta;
+    
+    // Gecko
+    if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
+        deltaY = 0;
+        deltaX = -1*delta;
+    }
+    
+    // Webkit
+    if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
+    if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
+    
+    // Add event and delta to the front of the arguments
+    args.unshift(event, delta, deltaX, deltaY);
+    
+    return $.event.handle.apply(this, args);
+}
+
+})(jQuery);
\ No newline at end of file
diff --git a/missing-sources/tablefilter/array.js b/missing-sources/tablefilter/array.js
new file mode 100644 (file)
index 0000000..dd10bc5
--- /dev/null
@@ -0,0 +1,17 @@
+/**
+ * Array utilities
+ */
+
+import Str from './string';
+
+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;
+    }
+};
diff --git a/missing-sources/tablefilter/cookie.js b/missing-sources/tablefilter/cookie.js
new file mode 100644 (file)
index 0000000..eeb3a93
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Cookie utilities
+ */
+
+export default {
+
+    write(name, value, hours){
+        let expire = '';
+        if(hours){
+            expire = new Date((new Date()).getTime() + hours * 3600000);
+            expire = '; expires=' + expire.toGMTString();
+        }
+        document.cookie = name + '=' + escape(value) + expire;
+    },
+
+    read(name){
+        let cookieValue = '',
+            search = name + '=';
+        if(document.cookie.length > 0){
+            let cookie = document.cookie,
+                offset = cookie.indexOf(search);
+            if(offset !== -1){
+                offset += search.length;
+                let end = cookie.indexOf(';', offset);
+                if(end === -1){
+                    end = cookie.length;
+                }
+                cookieValue = unescape(cookie.substring(offset, end));
+            }
+        }
+        return cookieValue;
+    },
+
+    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];
+    }
+
+};
diff --git a/missing-sources/tablefilter/date.js b/missing-sources/tablefilter/date.js
new file mode 100644 (file)
index 0000000..1eafe48
--- /dev/null
@@ -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;
+}
diff --git a/missing-sources/tablefilter/dom.js b/missing-sources/tablefilter/dom.js
new file mode 100644 (file)
index 0000000..175bda9
--- /dev/null
@@ -0,0 +1,142 @@
+/**
+ * DOM utilities
+ */
+
+export default {
+
+    /**
+     * 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;
+    },
+
+    /**
+     * 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;
+        }
+
+        let el = document.createElement(tag),
+            args = arguments;
+
+        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;
+    },
+
+    /**
+     * Returns a text node with given text
+     * @param  {String} txt
+     * @return {Object}
+     */
+    text(txt){
+        return document.createTextNode(txt);
+    },
+
+    hasClass(ele, cls){
+        if(!ele){ return false; }
+
+        if(supportsClassList()){
+            return ele.classList.contains(cls);
+        }
+        return ele.className.match(new RegExp('(\\s|^)'+ cls +'(\\s|$)'));
+    },
+
+    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(_id){
+        return document.getElementById(_id);
+    },
+
+    tag(o, tagname){
+        return o.getElementsByTagName(tagname);
+    }
+};
+
+// HTML5 classList API
+function supportsClassList(){
+    return document.documentElement.classList;
+}
diff --git a/missing-sources/tablefilter/event.js b/missing-sources/tablefilter/event.js
new file mode 100644 (file)
index 0000000..0b93b59
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * DOM event utilities
+ */
+
+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));
+    }
+};
diff --git a/missing-sources/tablefilter/extensions/advancedGrid/adapterEzEditTable.js b/missing-sources/tablefilter/extensions/advancedGrid/adapterEzEditTable.js
new file mode 100644 (file)
index 0000000..98dc802
--- /dev/null
@@ -0,0 +1,375 @@
+import Dom from '../../dom';
+
+export default class 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 {Object} tf TableFilter instance
+     */
+    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;
+        this.loadStylesheet = Boolean(cfg.load_stylesheet);
+        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 ?
+            false : tf.gridLayout;
+
+        this._ezEditTable = null;
+        this.cfg = cfg;
+        this.tf = tf;
+    }
+
+    /**
+     * Conditionally load ezEditTable library and set advanced grid
+     * @return {[type]} [description]
+     */
+    init(){
+        var tf = this.tf;
+        if(window.EditTable){
+            this._setAdvancedGrid();
+        } else {
+            var path = this.vendorPath + this.filename;
+            tf.import(this.filename, path, ()=> { this._setAdvancedGrid(); });
+        }
+        if(this.loadStylesheet && !tf.isImported(this.stylesheet, 'link')){
+            tf.import(this.stylesheetName, this.stylesheet, null, 'link');
+        }
+    }
+
+    /**
+     * Instantiate ezEditTable component for advanced grid features
+     */
+    _setAdvancedGrid(){
+        var tf = this.tf;
+
+        //start row for EditTable constructor needs to be calculated
+        var startRow,
+            cfg = this.cfg,
+            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){
+            startRow = undefined;
+        }
+        //otherwise startRow config property if any or TableFilter refRow
+        else{
+            startRow = cfg.startRow || tf.refRow;
+        }
+
+        cfg.base_path = cfg.base_path  || tf.basePath + 'ezEditTable/';
+        var editable = cfg.editable;
+        var selectable = cfg.selection;
+
+        if(selectable){
+            cfg.default_selection = cfg.default_selection || 'row';
+        }
+        //CSS Styles
+        cfg.active_cell_css = cfg.active_cell_css || 'ezETSelectedCell';
+
+        var _lastValidRowIndex = 0;
+        var _lastRowIndex = 0;
+
+        if(selectable){
+            //Row navigation needs to be calculated according to TableFilter's
+            //validRowsIndex array
+            var onAfterSelection = function(et, selectedElm, e){
+                var slc = et.Selection;
+                //Next valid filtered row needs to be selected
+                var doSelect = function(nextRowIndex){
+                    if(et.defaultSelection === 'row'){
+                        slc.SelectRowByIndex(nextRowIndex);
+                    } else {
+                        et.ClearSelections();
+                        var cellIndex = selectedElm.cellIndex,
+                            row = tf.tbl.rows[nextRowIndex];
+                        if(et.defaultSelection === 'both'){
+                            slc.SelectRowByIndex(nextRowIndex);
+                        }
+                        if(row){
+                            slc.SelectCell(row.cells[cellIndex]);
+                        }
+                    }
+                    //Table is filtered
+                    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){
+                                tf.tblCont.scrollLeft = 100000000;
+                            }
+                            else if(cell.cellIndex===0 && tf.gridLayout){
+                                tf.tblCont.scrollLeft = 0;
+                            } else {
+                                cell.scrollIntoView(false);
+                            }
+                        }
+                    }
+                };
+
+                //table is not filtered
+                if(!tf.validRowsIndex){
+                    return;
+                }
+                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,
+                    keyCode = e !== undefined ? et.Event.GetKey(e) : 0,
+                    isRowValid = validIndexes.indexOf(row.rowIndex) !== -1,
+                    nextRowIndex,
+                    paging = tf.feature('paging'),
+                    //pgup/pgdown keys
+                    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){
+                    //Selection direction up/down
+                    if(row.rowIndex>_lastRowIndex){
+                        //last row
+                        if(row.rowIndex >= validIndexes[validIdxLen-1]){
+                            nextRowIndex = validIndexes[validIdxLen-1];
+                        } else {
+                            var calcRowIndex = (_lastValidRowIndex + d);
+                            if(calcRowIndex > (validIdxLen-1)){
+                                nextRowIndex = validIndexes[validIdxLen-1];
+                            } else {
+                                nextRowIndex = validIndexes[calcRowIndex];
+                            }
+                        }
+                    } else{
+                        //first row
+                        if(row.rowIndex <= validIndexes[0]){
+                            nextRowIndex = validIndexes[0];
+                        } else {
+                            var v = validIndexes[_lastValidRowIndex - d];
+                            nextRowIndex = v ? v : validIndexes[0];
+                        }
+                    }
+                    _lastRowIndex = row.rowIndex;
+                    doSelect(nextRowIndex);
+                } else {
+                    //If filtered row is valid, special calculation for
+                    //pgup/pgdown keys
+                    if(keyCode!==34 && keyCode!==33){
+                        _lastValidRowIndex = validIndexes.indexOf(row.rowIndex);
+                        _lastRowIndex = row.rowIndex;
+                    } else {
+                        if(keyCode === 34){ //pgdown
+                            //last row
+                            if((_lastValidRowIndex + d) <= (validIdxLen-1)){
+                                nextRowIndex = validIndexes[
+                                _lastValidRowIndex + d];
+                            } else {
+                                nextRowIndex = [validIdxLen-1];
+                            }
+                        } else { //pgup
+                            //first row
+                            if((_lastValidRowIndex - d) <= validIndexes[0]){
+                                nextRowIndex = validIndexes[0];
+                            } else {
+                                nextRowIndex = validIndexes[
+                                    _lastValidRowIndex - d];
+                            }
+                        }
+                        _lastRowIndex = nextRowIndex;
+                        _lastValidRowIndex = validIndexes.indexOf(nextRowIndex);
+                        doSelect(nextRowIndex);
+                    }
+                }
+            };
+
+            //Page navigation has to be enforced whenever selected row is out of
+            //the current page range
+            var onBeforeSelection = function(et, selectedElm){
+                var row = et.defaultSelection !== 'row' ?
+                    selectedElm.parentNode : selectedElm;
+                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.pagingLength;
+                        var validIndexes = tf.validRowsIndex,
+                            validIdxLen = validIndexes.length,
+                            pagingEndRow = parseInt(paging.startPagingRow, 10) +
+                                parseInt(paging.pagingLength, 10);
+                        var rowIndex = row.rowIndex;
+
+                        if((rowIndex === validIndexes[validIdxLen-1]) &&
+                            paging.currentPageNb!==paging.nbPages){
+                            paging.setPage('last');
+                        }
+                        else if((rowIndex == validIndexes[0]) &&
+                            paging.currentPageNb!==1){
+                            paging.setPage('first');
+                        }
+                        else if(rowIndex > validIndexes[pagingEndRow-1] &&
+                            rowIndex < validIndexes[validIdxLen-1]){
+                            paging.setPage('next');
+                        }
+                        else if(
+                            rowIndex < validIndexes[paging.startPagingRow] &&
+                            rowIndex > validIndexes[0]){
+                            paging.setPage('previous');
+                        }
+                    }
+                }
+            };
+
+            //Selected row needs to be visible when paging is activated
+            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);
+                    }
+                    var cell = slc.GetActiveCell();
+                    if(cell){
+                        cell.scrollIntoView(false);
+                    }
+                };
+            }
+
+            //Rows navigation when rows are filtered is performed with the
+            //EditTable row selection callback events
+            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]);
+                    }
+                };
+                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 {
+                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]);
+                    }
+                };
+                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){
+            //Added or removed rows, TF rows number needs to be re-calculated
+            var fnE = cfg.on_added_dom_row;
+            cfg.on_added_dom_row = function(){
+                tf.nbFilterableRows++;
+                if(!tf.paging){
+                    tf.feature('rowsCounter').refresh();
+                } else {
+                    tf.nbRows++;
+                    tf.nbVisibleRows++;
+                    tf.nbFilterableRows++;
+                    tf.paging=false;
+                    tf.feature('paging').destroy();
+                    tf.feature('paging').reset();
+                }
+                if(tf.alternateRows){
+                    tf.feature('alternateRows').init();
+                }
+                if(fnE){
+                    fnE.call(null, arguments[0], arguments[1], arguments[2]);
+                }
+            };
+            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){
+                        tf.feature('rowsCounter').refresh();
+                    } else {
+                        tf.nbRows--;
+                        tf.nbVisibleRows--;
+                        tf.nbFilterableRows--;
+                        tf.paging=false;
+                        tf.feature('paging').destroy();
+                        tf.feature('paging').reset(false);
+                    }
+                    if(tf.alternateRows){
+                        tf.feature('alternateRows').init();
+                    }
+                    if(fnF){
+                        fnF.call(null, arguments[0], arguments[1]);
+                    }
+                };
+            }
+        }
+
+        try{
+            this._ezEditTable = new EditTable(tf.id, cfg, startRow);
+            this._ezEditTable.Init();
+        } catch(e) { throw new Error(this.err); }
+
+        this.initialized = true;
+    }
+
+    /**
+     * Reset advanced grid when previously removed
+     */
+    reset(){
+        var ezEditTable = this._ezEditTable;
+        if(ezEditTable){
+            if(this.cfg.selection){
+                ezEditTable.Selection.Set();
+            }
+            if(this.cfg.editable){
+                ezEditTable.Editable.Set();
+            }
+        }
+    }
+
+    /**
+     * Remove advanced grid
+     */
+    destroy(){
+        var ezEditTable = this._ezEditTable;
+        if(ezEditTable){
+            if(this.cfg.selection){
+                ezEditTable.Selection.ClearSelections();
+                ezEditTable.Selection.Remove();
+            }
+            if(this.cfg.editable){
+                ezEditTable.Editable.Remove();
+            }
+        }
+        this.initialized = false;
+    }
+}
diff --git a/missing-sources/tablefilter/extensions/advancedGrid/advancedGrid.js b/missing-sources/tablefilter/extensions/advancedGrid/advancedGrid.js
new file mode 100644 (file)
index 0000000..e548ef4
--- /dev/null
@@ -0,0 +1,3 @@
+import AdapterEzEditTable from './adapterEzEditTable';
+
+export default AdapterEzEditTable;
\ No newline at end of file
diff --git a/missing-sources/tablefilter/extensions/colOps/colOps.js b/missing-sources/tablefilter/extensions/colOps/colOps.js
new file mode 100644 (file)
index 0000000..aa9ca66
--- /dev/null
@@ -0,0 +1,315 @@
+import Dom from '../../dom';
+import Str from '../../string';
+import Types from '../../types';
+
+export default class ColOps{
+
+    /**
+     * Column calculations
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf, opts) {
+
+        //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;
+
+        this.opts = opts;
+        this.tf = tf;
+    }
+
+    init(){
+        this.calc();
+    }
+
+    /**
+     * Calculates columns' values
+     * Configuration options are stored in 'opts' property
+     * - 'id' contains ids of elements showing result (array)
+     * - 'col' contains the columns' indexes (array)
+     * - 'operation' contains operation type (array, values: 'sum', 'mean',
+     *   'min', 'max', 'median', 'q1', 'q3')
+     * - 'write_method' array defines which method to use for displaying the
+     *    result (innerHTML, setValue, createTextNode) - default: 'innerHTML'
+     * - 'tot_row_index' defines in which row results are displayed
+     *   (integers array)
+     *
+     * - changes made by Nuovella:
+     * (1) optimized the routine (now it will only process each column once),
+     * (2) added calculations for the median, lower and upper quartile.
+     */
+    calc() {
+        var tf = this.tf;
+        if(!tf.isFirstLoad && !tf.hasGrid()){
+            return;
+        }
+
+        if(this.onBeforeOperation){
+            this.onBeforeOperation.call(null, tf);
+        }
+
+        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
+        var ucolIndex = [],
+            ucolMax = 0;
+        ucolIndex[ucolMax] = colIndex[0];
+
+        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;
+                }
+            }
+            //if not saved then, save the index;
+            if (saved === 0){
+                ucolMax++;
+                ucolIndex[ucolMax] = colIndex[ii];
+            }
+        }
+
+        if(Str.lower(typeof labelId)=='object' &&
+            Str.lower(typeof colIndex)=='object' &&
+            Str.lower(typeof operation)=='object'){
+            var rows = tf.tbl.rows,
+                colvalues = [];
+
+            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));
+
+                //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;
+
+                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;
+
+                        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;
+                        }
+                    }
+                }
+
+                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);
+
+                    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];
+                    }
+                }
+
+                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;
+                    }
+
+                    var precision = !isNaN(decThisCol[i]) ? decThisCol[i] : 2;
+
+                    //if outputType is defined
+                    if(oTypeThisCol && result){
+                        result = result.toFixed( precision );
+
+                        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
+
+                // 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
+
+        if(this.onAfterOperation){
+            this.onAfterOperation.call(null, tf);
+        }
+    }
+
+    destroy(){}
+
+}
diff --git a/missing-sources/tablefilter/extensions/colsVisibility/colsVisibility.js b/missing-sources/tablefilter/extensions/colsVisibility/colsVisibility.js
new file mode 100644 (file)
index 0000000..5181861
--- /dev/null
@@ -0,0 +1,520 @@
+import Dom from '../../dom';
+import Types from '../../types';
+import Event from '../../event';
+
+export default class ColsVisibility{
+
+    /**
+     * Columns Visibility extension
+     * @param {Object} tf TableFilter instance
+     * @param {Object} f Config
+     */
+    constructor(tf, f){
+
+        // Configuration object
+        var cfg = tf.config();
+
+        this.initialized = false;
+        this.name = f.name;
+        this.desc = f.description || 'Columns visibility manager';
+
+        //show/hide cols span element
+        this.spanEl = null;
+        //show/hide cols button element
+        this.btnEl = null;
+        //show/hide cols container div element
+        this.contEl = null;
+
+        //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);
+        //enables select all option
+        this.enableTickAll = Boolean(f.enable_tick_all);
+        //text preceding columns list
+        this.tickAllText = f.tick_all_text || 'Select all:';
+
+        //array containing hidden columns indexes
+        this.hiddenCols = [];
+        this.tblHasColTag = (Dom.tag(tf.tbl,'col').length > 0);
+
+        //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 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;
+
+        //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.stylePath + this.stylesheet, null, 'link');
+
+        this.tf = tf;
+    }
+
+    toggle(){
+        var contDisplay = this.contEl.style.display;
+        var onBeforeOpen = this.onBeforeOpen;
+        var onBeforeClose = this.onBeforeClose;
+        var onAfterOpen = this.onAfterOpen;
+        var onAfterClose = this.onAfterClose;
+
+        if(onBeforeOpen && contDisplay !== 'inline'){
+            onBeforeOpen.call(null, this);
+        }
+        if(onBeforeClose && contDisplay === 'inline'){
+            onBeforeClose.call(null, this);
+        }
+
+        this.contEl.style.display = contDisplay === 'inline' ?
+            'none' : 'inline';
+
+        if(onAfterOpen && contDisplay !== 'inline'){
+            onAfterOpen.call(null, this);
+        }
+        if(onAfterClose && contDisplay === 'inline'){
+            onAfterClose.call(null, this);
+        }
+    }
+
+    checkItem(lbl){
+        var li = lbl.parentNode;
+        if(!li || !lbl){
+            return;
+        }
+        var isChecked = lbl.firstChild.checked;
+        var colIndex = lbl.firstChild.getAttribute('id').split('_')[1];
+        colIndex = parseInt(colIndex, 10);
+        if(isChecked){
+            Dom.addClass(li, this.listSlcItemCssClass);
+        } else {
+            Dom.removeClass(li, this.listSlcItemCssClass);
+        }
+
+        var hide = false;
+        if((this.tickToHide && isChecked) || (!this.tickToHide && !isChecked)){
+            hide = true;
+        }
+        this.setHidden(colIndex, hide);
+    }
+
+    init(){
+        if(!this.manager){
+            return;
+        }
+        this.buildBtn();
+        this.buildManager();
+
+        this.initialized = true;
+    }
+
+    /**
+     * Build main button UI
+     */
+    buildBtn(){
+        if(this.btnEl){
+            return;
+        }
+        var tf = this.tf;
+        var span = Dom.create('span', ['id', this.prfx+tf.id]);
+        span.className = this.spanCssClass;
+
+        //Container element (rdiv or custom element)
+        if(!this.btnTgtId){
+            tf.setToolbar();
+        }
+        var targetEl = !this.btnTgtId ? tf.rDiv : Dom.id(this.btnTgtId);
+
+        if(!this.btnTgtId){
+            var firstChild = targetEl.firstChild;
+            firstChild.parentNode.insertBefore(span, firstChild);
+        } else {
+            targetEl.appendChild(span);
+        }
+
+        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){
+                Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
+            } else {
+                Event.add(btn, 'mouseover', (evt)=> { this.toggle(evt); });
+            }
+        } else { //Custom html
+            span.innerHTML = this.btnHtml;
+            var colVisEl = span.firstChild;
+            if(!this.enableHover){
+                Event.add(colVisEl, 'click', (evt)=> { this.toggle(evt); });
+            } else {
+                Event.add(colVisEl, 'mouseover', (evt)=> { this.toggle(evt); });
+            }
+        }
+
+        this.spanEl = span;
+        this.btnEl = this.spanEl.firstChild;
+
+        if(this.onLoaded){
+            this.onLoaded.call(null, this);
+        }
+    }
+
+    /**
+     * Build columns manager UI
+     */
+    buildManager(){
+        var tf = this.tf;
+
+        var container = !this.contElTgtId ?
+            Dom.create('div', ['id', this.prfxCont+tf.id]) :
+            Dom.id(this.contElTgtId);
+        container.className = this.contCssClass;
+
+        //Extension description
+        var extNameLabel = Dom.create('p');
+        extNameLabel.innerHTML = this.text;
+        container.appendChild(extNameLabel);
+
+        //Headers list
+        var ul = Dom.create('ul' ,['id', 'ul'+this.name+'_'+tf.id]);
+        ul.className = this.listCssClass;
+
+        var tbl = this.headersTbl ? this.headersTbl : tf.tbl;
+        var headerIndex = this.headersTbl ?
+            this.headersIndex : tf.getHeadersRowIndex();
+        var headerRow = tbl.rows[headerIndex];
+
+        //Tick all option
+        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;
+
+            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;
+                    }
+                }
+            });
+        }
+
+        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){
+                liElm.check.checked = true;
+            }
+
+            Event.add(liElm.check, 'click', (evt)=> {
+                var elm = Event.target(evt);
+                var lbl = elm.parentNode;
+                this.checkItem(lbl);
+            });
+        }
+
+        //separator
+        var p = Dom.create('p', ['align','center']);
+        var btn;
+        //Close link
+        if(!this.btnCloseHtml){
+            btn = Dom.create('a', ['href','javascript:;']);
+            btn.className = this.btnCloseCssClass;
+            btn.innerHTML = this.btnCloseText;
+            Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
+            p.appendChild(btn);
+        } else {
+            p.innerHTML = this.btnCloseHtml;
+            btn = p.firstChild;
+            Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
+        }
+
+        container.appendChild(ul);
+        container.appendChild(p);
+
+        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 {Numner} colIndex Column index
+     * @param {Boolean} hide    hide column if true or show if false
+     */
+    setHidden(colIndex, hide){
+        var tf = this.tf;
+        var tbl = tf.tbl;
+
+        if(this.onBeforeColHidden && hide){
+            this.onBeforeColHidden.call(null, this, colIndex);
+        }
+        if(this.onBeforeColDisplayed && !hide){
+            this.onBeforeColDisplayed.call(null, this, colIndex);
+        }
+
+        this._hideCells(tbl, colIndex, hide);
+        if(this.headersTbl){
+            this._hideCells(this.headersTbl, colIndex, hide);
+        }
+
+        var hiddenCols = this.hiddenCols;
+        var itemIndex = hiddenCols.indexOf(colIndex);
+        if(hide){
+            if(itemIndex === -1){
+                this.hiddenCols.push(colIndex);
+            }
+        } else {
+            if(itemIndex !== -1){
+                this.hiddenCols.splice(itemIndex, 1);
+            }
+        }
+
+        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);
+        }
+    }
+
+    /**
+     * Show specified column
+     * @param  {Number} colIndex Column index
+     */
+    showCol(colIndex){
+        if(colIndex === undefined || !this.isColHidden(colIndex)){
+            return;
+        }
+        if(this.manager && this.contEl){
+            var itm = Dom.id('col_'+colIndex+'_'+this.tf.id);
+            if(itm){ itm.click(); }
+        } else {
+            this.setHidden(colIndex, false);
+        }
+    }
+
+    /**
+     * Hide specified column
+     * @param  {Number} colIndex Column index
+     */
+    hideCol(colIndex){
+        if(colIndex === undefined || this.isColHidden(colIndex)){
+            return;
+        }
+        if(this.manager && this.contEl){
+            var itm = Dom.id('col_'+colIndex+'_'+this.tf.id);
+            if(itm){ itm.click(); }
+        } else {
+            this.setHidden(colIndex, true);
+        }
+    }
+
+    /**
+     * Determine if specified column is hidden
+     * @param  {Number} colIndex Column index
+     */
+    isColHidden(colIndex){
+        if(this.hiddenCols.indexOf(colIndex) !== -1){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Toggle visibility of specified column
+     * @param  {Number} colIndex Column index
+     */
+    toggleCol(colIndex){
+        if(colIndex === undefined || this.isColHidden(colIndex)){
+            this.showCol(colIndex);
+        } else {
+            this.hideCol(colIndex);
+        }
+    }
+
+    /**
+     * Returns the indexes of the columns currently hidden
+     * @return {Array} column indexes
+     */
+    getHiddenCols(){
+        return this.hiddenCols;
+    }
+
+    /**
+     * Remove the columns manager
+     */
+    destroy(){
+        if(!this.btnEl && !this.contEl){
+            return;
+        }
+        if(Dom.id(this.contElTgtId)){
+            Dom.id(this.contElTgtId).innerHTML = '';
+        } else {
+            this.contEl.innerHTML = '';
+            this.contEl.parentNode.removeChild(this.contEl);
+            this.contEl = null;
+        }
+        this.btnEl.innerHTML = '';
+        this.btnEl.parentNode.removeChild(this.btnEl);
+        this.btnEl = null;
+        this.initialized = false;
+    }
+
+    _getHeaderText(cell){
+        if(!cell.hasChildNodes){
+            return '';
+        }
+
+        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){
+                    continue;
+                } else {
+                    return Dom.getText(n);
+                }
+            }
+            continue;
+        }
+        return '';
+    }
+
+    _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' : '';
+            }
+        }
+    }
+
+}
diff --git a/missing-sources/tablefilter/extensions/filtersVisibility/filtersVisibility.js b/missing-sources/tablefilter/extensions/filtersVisibility/filtersVisibility.js
new file mode 100644 (file)
index 0000000..962079b
--- /dev/null
@@ -0,0 +1,184 @@
+import Dom from '../../dom';
+import Types from '../../types';
+import Event from '../../event';
+
+export default class FiltersVisibility{
+
+    /**
+     * Filters Row Visibility extension
+     * @param {Object} tf TableFilter instance
+     * @param {Object} f Config
+     */
+    constructor(tf, f){
+
+        this.initialized = false;
+        this.name = f.name;
+        this.desc = 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';
+
+        //expand/collapse filters span element
+        this.contEl = null;
+        //expand/collapse filters btn element
+        this.btnEl = null;
+
+        this.icnExpandHtml = '<img src="'+ tf.themesPath + this.icnExpand +
+            '" alt="Expand filters" >';
+        this.icnCollapseHtml = '<img src="'+ tf.themesPath + this.icnCollapse +
+            '" alt="Collapse filters" >';
+        this.defaultText = 'Toggle filters';
+
+        //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 || '';
+
+        //defines expand/collapse filters text
+        this.collapseBtnHtml = this.enableIcon ?
+            this.icnCollapseHtml + this.btnText :
+            this.btnText || this.defaultText;
+        this.expandBtnHtml =  this.enableIcon ?
+            this.icnExpandHtml + this.btnText :
+            this.btnText || this.defaultText;
+
+        //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();
+
+        this.visibleAtStart = !Types.isUndef(f.visible_at_start) ?
+            Boolean(f.visible_at_start) : true;
+
+        // Prefix
+        this.prfx = 'fltsVis_';
+
+        //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;
+
+        //Loads extension stylesheet
+        tf.import(f.name+'Style', tf.stylePath + this.stylesheet, null, 'link');
+
+        this.tf = tf;
+    }
+
+    /**
+     * Initialise extension
+     */
+    init(){
+        if(this.initialized){
+            return;
+        }
+
+        this.buildUI();
+        this.initialized = true;
+    }
+
+    /**
+     * Build UI elements
+     */
+    buildUI(){
+        let tf = this.tf;
+        let span = Dom.create('span',['id', this.prfx+tf.id]);
+        span.className = this.contCssClass;
+
+        //Container element (rdiv or custom element)
+        if(!this.targetId){
+            tf.setToolbar();
+        }
+        let targetEl = !this.targetId ? tf.rDiv : Dom.id(this.targetId);
+
+        if(!this.targetId){
+            let firstChild = targetEl.firstChild;
+            firstChild.parentNode.insertBefore(span, firstChild);
+        } else {
+            targetEl.appendChild(span);
+        }
+
+        let btn;
+        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
+            span.innerHTML = this.btnHtml;
+            btn = span.firstChild;
+        }
+
+        Event.add(btn, 'click', ()=> this.toggle());
+
+        this.contEl = span;
+        this.btnEl = btn;
+
+        if(!this.visibleAtStart){
+            this.toggle();
+        }
+    }
+
+    /**
+     * Toggle filters visibility
+     */
+    toggle(){
+        let tf = this.tf;
+        let tbl = tf.gridLayout? tf.feature('gridLayout').headTbl : tf.tbl;
+        let fltRow = tbl.rows[this.filtersRowIndex];
+        let fltRowDisplay = fltRow.style.display;
+
+        if(this.onBeforeShow && fltRowDisplay !== ''){
+            this.onBeforeShow.call(this, this);
+        }
+        if(this.onBeforeHide && fltRowDisplay === ''){
+            this.onBeforeHide.call(null, this);
+        }
+
+        fltRow.style.display = fltRowDisplay==='' ? 'none' : '';
+        if(this.enableIcon && !this.btnHtml){
+            this.btnEl.innerHTML = fltRowDisplay === '' ?
+                this.expandBtnHtml : this.collapseBtnHtml;
+        }
+
+        if(this.onAfterShow && fltRowDisplay !== ''){
+            this.onAfterShow.call(null, this);
+        }
+        if(this.onAfterHide && fltRowDisplay === ''){
+            this.onAfterHide.call(null, this);
+        }
+    }
+
+    /**
+     * Destroy the UI
+     */
+    destroy(){
+        if(!this.btnEl && !this.contEl){
+            return;
+        }
+
+        this.btnEl.innerHTML = '';
+        this.btnEl.parentNode.removeChild(this.btnEl);
+        this.btnEl = null;
+
+        this.contEl.innerHTML = '';
+        this.contEl.parentNode.removeChild(this.contEl);
+        this.contEl = null;
+        this.initialized = false;
+    }
+
+}
diff --git a/missing-sources/tablefilter/extensions/sort/adapterSortabletable.js b/missing-sources/tablefilter/extensions/sort/adapterSortabletable.js
new file mode 100644 (file)
index 0000000..9b5e199
--- /dev/null
@@ -0,0 +1,460 @@
+import Types from '../../types';
+import Dom from '../../dom';
+import Event from '../../event';
+import DateHelper from '../../date';
+import Helpers from '../../helpers';
+
+export default class AdapterSortableTable{
+
+    /**
+     * SortableTable Adapter module
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf, opts){
+        this.initialized = false;
+        this.name = opts.name;
+        this.desc = opts.description || 'Sortable table';
+
+        //indicates if paging is enabled
+        this.isPaged = false;
+
+        //indicates if tables was sorted
+        this.sorted = false;
+
+        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);
+        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
+        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';
+
+        /*** TF additional events ***/
+        //additional paging events for alternating background
+        // o.Evt._Paging.nextEvt = function(){
+        // if(o.sorted && o.alternateRows) o.Filter();
+        // }
+        // o.Evt._Paging.prevEvt = o.Evt._Paging.nextEvt;
+        // o.Evt._Paging.firstEvt = o.Evt._Paging.nextEvt;
+        // o.Evt._Paging.lastEvt = o.Evt._Paging.nextEvt;
+        // o.Evt._OnSlcPagesChangeEvt = o.Evt._Paging.nextEvt;
+
+        // 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;
+
+        this.tf = tf;
+    }
+
+    init(){
+        let tf = this.tf;
+        let adpt = this;
+
+        // SortableTable class sanity check (sortabletable.js)
+        if(Types.isUndef(SortableTable)){
+            throw new Error('SortableTable class not found.');
+        }
+
+        this.overrideSortableTable();
+        this.setSortTypes();
+
+        //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(){
+            if(adpt.onBeforeSort){
+                adpt.onBeforeSort.call(null, tf, adpt.stt.sortColumn);
+            }
+
+            /*** sort behaviour for paging ***/
+            if(tf.paging){
+                adpt.isPaged = true;
+                tf.paging = false;
+                tf.feature('paging').destroy();
+            }
+        };
+
+        this.stt.onsort = function(){
+            adpt.sorted = true;
+
+            //rows alternating bg issue
+            // TODO: move into AlternateRows component
+            if(tf.alternateRows){
+                let rows = tf.tbl.rows, c = 0;
+
+                let setClass = function(row, i, removeOnly){
+                    if(Types.isUndef(removeOnly)){
+                        removeOnly = false;
+                    }
+                    let altRows = tf.feature('alternateRows'),
+                        oddCls = altRows.oddCss,
+                        evenCls = altRows.evenCss;
+                    Dom.removeClass(row, oddCls);
+                    Dom.removeClass(row, evenCls);
+
+                    if(!removeOnly){
+                        Dom.addClass(row, i % 2 ? oddCls : evenCls);
+                    }
+                };
+
+                for (let i = tf.refRow; i < tf.nbRows; i++){
+                    let isRowValid = rows[i].getAttribute('validRow');
+                    if(tf.paging && rows[i].style.display === ''){
+                        setClass(rows[i], c);
+                        c++;
+                    } else {
+                        if((isRowValid==='true' || isRowValid===null) &&
+                            rows[i].style.display === ''){
+                            setClass(rows[i], c);
+                            c++;
+                        } else {
+                            setClass(rows[i], c, true);
+                        }
+                    }
+                }
+            }
+            //sort behaviour for paging
+            if(adpt.isPaged){
+                let paginator = tf.feature('paging');
+                paginator.reset(false);
+                paginator.setPage(paginator.getPage());
+                adpt.isPaged = false;
+            }
+
+            if(adpt.onAfterSort){
+                adpt.onAfterSort.call(null, tf, adpt.stt.sortColumn);
+            }
+        };
+
+        this.initialized = true;
+    }
+
+    /**
+     * Sort specified column
+     * @param {Number} colIdx Column index
+     * @param {Boolean} desc Optional: descending manner
+     */
+    sortByColumnIndex(colIdx, desc){
+        this.stt.sort(colIdx, desc);
+    }
+
+    overrideSortableTable(){
+        let adpt = this,
+            tf = this.tf;
+
+        /**
+         * Overrides headerOnclick method in order to handle th event
+         * @param  {Object} e [description]
+         */
+        SortableTable.prototype.headerOnclick = function(evt){
+            if(!adpt.initialized){
+                return;
+            }
+
+            // find Header element
+            let el = evt.target || evt.srcElement;
+
+            while(el.tagName !== 'TD' && el.tagName !== 'TH'){
+                el = el.parentNode;
+            }
+
+            this.sort(
+                SortableTable.msie ?
+                    SortableTable.getCellIndex(el) : el.cellIndex
+            );
+        };
+
+        /**
+         * Overrides getCellIndex IE returns wrong cellIndex when columns are
+         * hidden
+         * @param  {Object} oTd TD element
+         * @return {Number}     Cell index
+         */
+        SortableTable.getCellIndex = function(oTd){
+            let cells = oTd.parentNode.cells,
+                l = cells.length, i;
+            for (i = 0; cells[i] != oTd && i < l; i++){}
+            return i;
+        };
+
+        /**
+         * Overrides initHeader in order to handle filters row position
+         * @param  {Array} oSortTypes
+         */
+        SortableTable.prototype.initHeader = function(oSortTypes){
+            let stt = this;
+            if (!stt.tHead){
+                if(tf.gridLayout){
+                    stt.tHead = tf.feature('gridLayout').headTbl.tHead;
+                } else {
+                    return;
+                }
+            }
+
+            stt.headersRow = tf.headersRow;
+            let cells = stt.tHead.rows[stt.headersRow].cells;
+            stt.sortTypes = oSortTypes || [];
+            let l = cells.length;
+            let img, c;
+
+            for (let i = 0; i < l; i++) {
+                c = cells[i];
+                if (stt.sortTypes[i] !== null && stt.sortTypes[i] !== 'None'){
+                    c.style.cursor = 'pointer';
+                    img = Dom.create('img',
+                        ['src', adpt.imgPath + adpt.imgBlank]);
+                    c.appendChild(img);
+                    if (stt.sortTypes[i] !== null){
+                        c.setAttribute( '_sortType', stt.sortTypes[i]);
+                    }
+                    Event.add(c, 'click', stt._headerOnclick);
+                } else {
+                    c.setAttribute('_sortType', oSortTypes[i]);
+                    c._sortType = 'None';
+                }
+            }
+            stt.updateHeaderArrows();
+        };
+
+        /**
+         * Overrides updateHeaderArrows in order to handle arrows indicators
+         */
+        SortableTable.prototype.updateHeaderArrows = function(){
+            let stt = this;
+            let cells, l, img;
+
+            // external headers
+            if(adpt.asyncSort && adpt.triggerIds.length > 0){
+                let triggers = adpt.triggerIds;
+                cells = [];
+                l = triggers.length;
+                for(let j=0; j<triggers.length; j++){
+                    cells.push(Dom.id(triggers[j]));
+                }
+            } else {
+                if(!this.tHead){
+                    return;
+                }
+                cells = stt.tHead.rows[stt.headersRow].cells;
+                l = cells.length;
+            }
+            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]);
+                        cells[i].appendChild(img);
+                    }
+                    if (i === stt.sortColumn){
+                        img.className = adpt.imgClassName +' '+
+                            (this.descending ?
+                                adpt.imgDescClassName :
+                                adpt.imgAscClassName);
+                    } else{
+                        img.className = adpt.imgClassName;
+                    }
+                }
+            }
+        };
+
+        /**
+         * Overrides getRowValue for custom key value feature
+         * @param  {Object} oRow    Row element
+         * @param  {String} sType
+         * @param  {Number} nColumn
+         * @return {String}
+         */
+        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){
+                return sortTypeInfo.getRowValue(oRow, nColumn);
+            }
+            let c = oRow.cells[nColumn];
+            let s = SortableTable.getInnerText(c);
+            return stt.getValueFromString(s, sType);
+        };
+
+        /**
+         * Overrides getInnerText in order to avoid Firefox unexpected sorting
+         * behaviour with untrimmed text elements
+         * @param  {Object} oNode DOM element
+         * @return {String}       DOM element inner text
+         */
+        SortableTable.getInnerText = function(oNode){
+            if(!oNode){
+                return;
+            }
+            if(oNode.getAttribute(adpt.customKey)){
+                return oNode.getAttribute(adpt.customKey);
+            } else {
+                return Dom.getText(oNode);
+            }
+        };
+    }
+
+    addSortType(){
+        var args = arguments;
+        SortableTable.prototype.addSortType(args[0], args[1], args[2], args[3]);
+    }
+
+    setSortTypes(){
+        let tf = this.tf,
+            sortTypes = this.sortTypes,
+            _sortTypes = [];
+
+        for(let i=0; i<tf.nbCells; i++){
+            let colType;
+
+            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';
+                }
+            }
+            _sortTypes.push(colType);
+        }
+
+        //Public TF method to add sort type
+
+        //Custom sort types
+        this.addSortType('number', Number);
+        this.addSortType('caseinsensitivestring', SortableTable.toUpperCase);
+        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.tbl, _sortTypes);
+
+        /*** external table headers adapter ***/
+        if(this.asyncSort && this.triggerIds.length > 0){
+            let triggers = this.triggerIds;
+            for(let j=0; j<triggers.length; j++){
+                if(triggers[j] === null){
+                    continue;
+                }
+                let trigger = Dom.id(triggers[j]);
+                if(trigger){
+                    trigger.style.cursor = 'pointer';
+
+                    Event.add(trigger, 'click', (evt) => {
+                        let elm = evt.target;
+                        if(!this.tf.sort){
+                            return;
+                        }
+                        this.stt.asyncSort(triggers.indexOf(elm.id));
+                    });
+                    trigger.setAttribute('_sortType', _sortTypes[j]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Destroy sort
+     */
+    destroy(){
+        let tf = this.tf;
+        this.sorted = false;
+        this.initialized = false;
+        this.stt.destroy();
+
+        let ids = tf.getFiltersId();
+        for (let idx = 0; idx < ids.length; idx++){
+            let header = tf.getHeaderElement(idx);
+            let img = Dom.tag(header, 'img');
+
+            if(img.length === 1){
+                header.removeChild(img[0]);
+            }
+        }
+    }
+
+}
+
+//Converters
+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('.');
+    for (let x in vals) {
+        let val = vals[x];
+        while (3 > val.length){
+            val = '0'+val;
+        }
+        vals[x] = val;
+    }
+    return vals.join('.');
+}
+
+function sortIP(a,b){
+    let aa = ipAddress(a.value.toLowerCase());
+    let bb = ipAddress(b.value.toLowerCase());
+    if (aa==bb){
+        return 0;
+    } else if (aa<bb){
+        return -1;
+    } else {
+        return 1;
+    }
+}
diff --git a/missing-sources/tablefilter/extensions/sort/sort.js b/missing-sources/tablefilter/extensions/sort/sort.js
new file mode 100644 (file)
index 0000000..8ccf001
--- /dev/null
@@ -0,0 +1,8 @@
+// import 'script!sortabletable';
+import AdapterSortableTable from './adapterSortabletable';
+
+if(!window.SortableTable){
+    require('script!sortabletable');
+}
+
+export default AdapterSortableTable;
diff --git a/missing-sources/tablefilter/helpers.js b/missing-sources/tablefilter/helpers.js
new file mode 100644 (file)
index 0000000..0c2e420
--- /dev/null
@@ -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;
+    }
+};
diff --git a/missing-sources/tablefilter/modules/alternateRows.js b/missing-sources/tablefilter/modules/alternateRows.js
new file mode 100644 (file)
index 0000000..c9099b4
--- /dev/null
@@ -0,0 +1,95 @@
+import {Feature} from './feature';
+import Dom from '../dom';
+
+export class AlternateRows extends Feature {
+
+    /**
+     * Alternating rows color
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf) {
+        super(tf, 'alternateRows');
+
+        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){
+            return;
+        }
+
+        var tf = this.tf;
+        var validRowsIndex = tf.validRowsIndex;
+        var noValidRowsIndex = validRowsIndex===null;
+        //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(var j=beginIndex; j<indexLen; j++){
+            var rowIdx = noValidRowsIndex ? j : validRowsIndex[j];
+            this.setRowBg(rowIdx, idx);
+            idx++;
+        }
+        this.initialized = true;
+    }
+
+    /**
+     * Sets row background color
+     * @param {Number} rowIdx Row index
+     * @param {Number} idx    Valid rows collection index needed to calculate bg
+     * color
+     */
+    setRowBg(rowIdx, idx) {
+        if(!this.isEnabled() || isNaN(rowIdx)){
+            return;
+        }
+        var rows = this.tf.tbl.rows;
+        var i = isNaN(idx) ? rowIdx : idx;
+        this.removeRowBg(rowIdx);
+
+        Dom.addClass(
+            rows[rowIdx],
+            (i%2) ? this.evenCss : this.oddCss
+        );
+    }
+
+    /**
+     * Removes row background color
+     * @param  {Number} idx Row index
+     */
+    removeRowBg(idx) {
+        if(isNaN(idx)){
+            return;
+        }
+        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){
+            return;
+        }
+        for(var i=this.tf.refRow; i<this.tf.nbRows; i++){
+            this.removeRowBg(i);
+        }
+        this.disable();
+        this.initialized = false;
+    }
+
+}
diff --git a/missing-sources/tablefilter/modules/checkList.js b/missing-sources/tablefilter/modules/checkList.js
new file mode 100644 (file)
index 0000000..9753834
--- /dev/null
@@ -0,0 +1,419 @@
+import Dom from '../dom';
+import Arr from '../array';
+import Str from '../string';
+import Sort from '../sort';
+import Event from '../event';
+
+export class CheckList{
+
+    /**
+     * Checklist UI component
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        // Configuration object
+        var f = tf.config();
+
+        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_';
+
+        this.isCustom = null;
+        this.opts = null;
+        this.optsTxt = null;
+        this.excludedOpts = null;
+
+        this.tf = tf;
+    }
+
+    // TODO: move event here
+    onChange(evt){
+        let elm = evt.target;
+        this.tf.activeFilterId = elm.getAttribute('id');
+        this.tf.activeFlt = Dom.id(this.tf.activeFilterId);
+        this.tf.Evt.onSlcChange.call(this.tf, evt);
+    }
+
+    optionClick(evt){
+        this.setCheckListValues(evt.target);
+        this.onChange(evt);
+    }
+
+    /**
+     * Build checklist UI asynchronously
+     * @param  {Number}  colIndex   Column index
+     * @param  {Boolean} isExternal Render in external container
+     * @param  {String}  extFltId   External container id
+     */
+    build(colIndex, isExternal, extFltId){
+        var tf = this.tf;
+        tf.EvtManager(
+            tf.Evt.name.checklist,
+            { slcIndex:colIndex, slcExternal:isExternal, slcId:extFltId }
+        );
+    }
+
+    /**
+     * Build checklist UI
+     * @param  {Number}  colIndex   Column index
+     * @param  {Boolean} isExternal Render in external container
+     * @param  {String}  extFltId   External container id
+     */
+    _build(colIndex, isExternal=false, extFltId=null){
+        var tf = this.tf;
+        colIndex = parseInt(colIndex, 10);
+
+        this.opts = [];
+        this.optsTxt = [];
+
+        var divFltId = this.prfxCheckListDiv+colIndex+'_'+tf.id;
+        if((!Dom.id(divFltId) && !isExternal) ||
+            (!Dom.id(extFltId) && isExternal)){
+            return;
+        }
+
+        var flt = !isExternal ? this.checkListDiv[colIndex] : Dom.id(extFltId);
+        var ul = Dom.create(
+            'ul', ['id', tf.fltIds[colIndex]], ['colIndex', colIndex]);
+        ul.className = this.checkListCssClass;
+        Event.add(ul, 'change', (evt) => { this.onChange(evt); });
+
+        var rows = tf.tbl.rows;
+        this.isCustom = tf.isCustomOptions(colIndex);
+
+        var activeFlt;
+        if(tf.linkedFilters && tf.activeFilterId){
+            activeFlt = tf.activeFilterId.split('_')[0];
+            activeFlt = activeFlt.split(tf.prfxFlt)[1];
+        }
+
+        var filteredDataCol = [];
+        if(tf.linkedFilters && tf.disableExcludedOptions){
+            this.excludedOpts = [];
+        }
+
+        for(var 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;
+            }
+
+            var cells = rows[k].cells;
+            var ncells = cells.length;
+
+            // checks if row has exact cell #
+            if(ncells !== tf.nbCells || this.isCustom){
+                continue;
+            }
+
+            // this loop retrieves cell data
+            for(var 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)) )))){
+                    var cell_data = tf.getCellData(cells[j]);
+                    //Vary Peter's patch
+                    var 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);
+                    }
+                    var 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) &&
+                            !tf.isFirstLoad){
+                            this.excludedOpts.push(cell_data);
+                        }
+                    }
+                }
+            }
+        }
+
+        //Retrieves custom values
+        if(this.isCustom){
+            var customValues = tf.getCustomOptions(colIndex);
+            this.opts = customValues[0];
+            this.optsTxt = customValues[1];
+        }
+
+        if(tf.sortSlc && !this.isCustom){
+            if (!tf.matchCase){
+                this.opts.sort(Sort.ignoreCase);
+                if(this.excludedOpts){
+                    this.excludedOpts.sort(Sort.ignoreCase);
+                }
+            } else {
+                this.opts.sort();
+                if(this.excludedOpts){
+                    this.excludedOpts.sort();
+                }
+            }
+        }
+        //asc sort
+        if(tf.sortNumAsc && tf.sortNumAsc.indexOf(colIndex) != -1){
+            try{
+                this.opts.sort(numSortAsc);
+                if(this.excludedOpts){
+                    this.excludedOpts.sort(numSortAsc);
+                }
+                if(this.isCustom){
+                    this.optsTxt.sort(numSortAsc);
+                }
+            } catch(e) {
+                this.opts.sort();
+                if(this.excludedOpts){
+                    this.excludedOpts.sort();
+                }
+                if(this.isCustom){
+                    this.optsTxt.sort();
+                }
+            }//in case there are alphanumeric values
+        }
+        //desc sort
+        if(tf.sortNumDesc && tf.sortNumDesc.indexOf(colIndex) != -1){
+            try{
+                this.opts.sort(numSortDesc);
+                if(this.excludedOpts){
+                    this.excludedOpts.sort(numSortDesc);
+                }
+                if(this.isCustom){
+                    this.optsTxt.sort(numSortDesc);
+                }
+            } catch(e) {
+                this.opts.sort();
+                if(this.excludedOpts){
+                    this.excludedOpts.sort(); }
+                if(this.isCustom){
+                    this.optsTxt.sort();
+                }
+            }//in case there are alphanumeric values
+        }
+
+        this.addChecks(colIndex, ul, tf.separator);
+
+        if(tf.loadFltOnDemand){
+            flt.innerHTML = '';
+        }
+        flt.appendChild(ul);
+        flt.setAttribute('filled', '1');
+    }
+
+    /**
+     * Add checklist options
+     * @param {Number} colIndex  Column index
+     * @param {Object} ul        Ul element
+     */
+    addChecks(colIndex, ul){
+        var tf = this.tf;
+        var chkCt = this.addTChecks(colIndex, ul);
+        var fltArr = []; //remember grid values
+        var store = tf.feature('store');
+        var tmpVal = store ?
+                store.getFilterValues(tf.fltsValuesCookie)[colIndex] : null;
+        if(tmpVal && Str.trim(tmpVal).length > 0){
+            if(tf.hasCustomSlcOptions &&
+                tf.customSlcOptions.cols.indexOf(colIndex) != -1){
+                fltArr.push(tmpVal);
+            } else {
+                fltArr = tmpVal.split(' '+tf.orOperator+' ');
+            }
+        }
+
+        for(var y=0; y<this.opts.length; y++){
+            var val = this.opts[y]; //item value
+            var lbl = this.isCustom ? this.optsTxt[y] : val; //item text
+            var 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 {
+                Event.add(li.check, 'click',
+                    (evt) => { this.optionClick(evt); });
+            }
+            ul.appendChild(li);
+
+            if(val===''){
+                //item is hidden
+                li.style.display = 'none';
+            }
+
+            /*** remember grid values ***/
+            if(tf.rememberGridValues){
+                if((tf.hasCustomSlcOptions &&
+                    tf.customSlcOptions.cols.indexOf(colIndex) != -1 &&
+                    fltArr.toString().indexOf(val) != -1) ||
+                    Arr.has(fltArr,
+                        Str.matchCase(val, tf.matchCase), tf.matchCase)){
+                    li.check.checked = true;
+                    this.setCheckListValues(li.check);
+                }
+            }
+        }
+    }
+
+    /**
+     * Add checklist header option
+     * @param {Number} colIndex Column index
+     * @param {Object} ul       Ul element
+     */
+    addTChecks(colIndex, ul){
+        var tf = this.tf;
+        var chkCt = 1;
+        var li0 = Dom.createCheckItem(
+                    tf.fltIds[colIndex]+'_0', '', tf.displayAllText);
+        li0.className = this.checkListItemCssClass;
+        ul.appendChild(li0);
+
+        Event.add(li0.check, 'click', (evt) => {
+            this.optionClick(evt);
+        });
+
+        if(!this.enableCheckListResetFilter){
+            li0.style.display = 'none';
+        }
+
+        if(tf.enableEmptyOption){
+            var li1 = Dom.createCheckItem(
+                    tf.fltIds[colIndex]+'_1', tf.emOperator, tf.emptyText);
+            li1.className = this.checkListItemCssClass;
+            ul.appendChild(li1);
+            Event.add(li1.check, 'click', (evt) => {
+                this.optionClick(evt);
+            });
+            chkCt++;
+        }
+
+        if(tf.enableNonEmptyOption){
+            var li2 = Dom.createCheckItem(
+                tf.fltIds[colIndex]+'_2',
+                tf.nmOperator,
+                tf.nonEmptyText
+            );
+            li2.className = this.checkListItemCssClass;
+            ul.appendChild(li2);
+            Event.add(li2.check, 'click', (evt) => {
+                this.optionClick(evt);
+            });
+            chkCt++;
+        }
+        return chkCt;
+    }
+
+    /**
+     * Store checked options in DOM element attribute
+     * @param {Object} o checklist option DOM element
+     */
+    setCheckListValues(o){
+        if(!o){
+            return;
+        }
+        var tf = this.tf;
+        var chkValue = o.value; //checked item value
+        var chkIndex = parseInt(o.id.split('_')[2], 10);
+        var filterTag = 'ul', itemTag = 'li';
+        var n = o;
+
+        //ul tag search
+        while(Str.lower(n.nodeName)!==filterTag){
+            n = n.parentNode;
+        }
+
+        var li = n.childNodes[chkIndex];
+        var colIndex = n.getAttribute('colIndex');
+        var fltValue = n.getAttribute('value'); //filter value (ul tag)
+        var fltIndexes = n.getAttribute('indexes'); //selected items (ul tag)
+
+        if(o.checked){
+            //show all item
+            if(chkValue===''){
+                if((fltIndexes && fltIndexes!=='')){
+                    //items indexes
+                    var indSplit = fltIndexes.split(tf.separator);
+                    //checked items loop
+                    for(var u=0; u<indSplit.length; u++){
+                        //checked item
+                        var 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 {
+                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;
+                }
+            }
+
+            if(Str.lower(li.nodeName) === itemTag){
+                Dom.removeClass(
+                    n.childNodes[0], this.checkListSlcItemCssClass);
+                Dom.addClass(li, this.checkListSlcItemCssClass);
+            }
+        } else { //removes values and indexes
+            if(chkValue!==''){
+                var replaceValue = new RegExp(
+                        Str.rgxEsc(chkValue+' '+tf.orOperator));
+                fltValue = fltValue.replace(replaceValue,'');
+                n.setAttribute('value', Str.trim(fltValue));
+
+                var 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);
+            }
+        }
+    }
+}
diff --git a/missing-sources/tablefilter/modules/clearButton.js b/missing-sources/tablefilter/modules/clearButton.js
new file mode 100644 (file)
index 0000000..6af1d84
--- /dev/null
@@ -0,0 +1,95 @@
+import {Feature} from './feature';
+import Dom from '../dom';
+import Event from '../event';
+
+export class ClearButton extends Feature{
+
+    /**
+     * Clear button component
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        super(tf, 'btnReset');
+
+        // Configuration object
+        var f = this.config;
+
+        //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_';
+    }
+
+    onClick(){
+        if(!this.isEnabled()){
+            return;
+        }
+        this.tf.clearFilters();
+    }
+
+    /**
+     * Build DOM elements
+     */
+    init(){
+        var tf = this.tf;
+
+        if(this.initialized){
+            return;
+        }
+
+        var resetspan = Dom.create('span', ['id', this.prfxResetSpan+tf.id]);
+
+        // reset button is added to defined element
+        if(!this.btnResetTgtId){
+            tf.setToolbar();
+        }
+        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;
+
+        this.initialized = true;
+    }
+
+    /**
+     * Remove clear button UI
+     */
+    destroy(){
+        var tf = this.tf;
+
+        if(!this.initialized){
+            return;
+        }
+
+        var resetspan = Dom.id(this.prfxResetSpan+tf.id);
+        if(resetspan){
+            resetspan.parentNode.removeChild(resetspan);
+        }
+        this.btnResetEl = null;
+        this.disable();
+        this.initialized = false;
+    }
+}
diff --git a/missing-sources/tablefilter/modules/dropdown.js b/missing-sources/tablefilter/modules/dropdown.js
new file mode 100644 (file)
index 0000000..a033e65
--- /dev/null
@@ -0,0 +1,342 @@
+import Dom from '../dom';
+import Arr from '../array';
+import Str from '../string';
+import Sort from '../sort';
+
+export class Dropdown{
+
+    /**
+     * Dropdown UI component
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        // Configuration object
+        var f = tf.config();
+
+        this.enableSlcResetFilter = f.enable_slc_reset_filter===false ?
+            false : true;
+        //defines empty option text
+        this.nonEmptyText = f.non_empty_text || '(Non empty)';
+        //sets select filling method: 'innerHTML' or 'createElement'
+        this.slcFillingMethod = f.slc_filling_method || 'createElement';
+        //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';
+
+        this.isCustom = null;
+        this.opts = null;
+        this.optsTxt = null;
+        this.slcInnerHtml = null;
+
+        this.tf = tf;
+    }
+
+    /**
+     * Build drop-down filter UI asynchronously
+     * @param  {Number}  colIndex   Column index
+     * @param  {Boolean} isLinked Enable linked refresh behaviour
+     * @param  {Boolean} isExternal Render in external container
+     * @param  {String}  extSlcId   External container id
+     */
+    build(colIndex, isLinked, isExternal, extSlcId){
+        var tf = this.tf;
+        tf.EvtManager(
+            tf.Evt.name.dropdown,
+            {
+                slcIndex: colIndex,
+                slcRefreshed: isLinked,
+                slcExternal: isExternal,
+                slcId: extSlcId
+            }
+        );
+    }
+
+    /**
+     * Build drop-down filter UI
+     * @param  {Number}  colIndex    Column index
+     * @param  {Boolean} isLinked Enable linked refresh behaviour
+     * @param  {Boolean} isExternal  Render in external container
+     * @param  {String}  extSlcId    External container id
+     */
+    _build(colIndex, isLinked=false, isExternal=false, extSlcId=null){
+        var tf = this.tf;
+        colIndex = parseInt(colIndex, 10);
+
+        this.opts = [];
+        this.optsTxt = [];
+        this.slcInnerHtml = '';
+
+        var slcId = tf.fltIds[colIndex];
+        if((!Dom.id(slcId) && !isExternal) ||
+            (!Dom.id(extSlcId) && isExternal)){
+            return;
+        }
+        var slc = !isExternal ? Dom.id(slcId) : Dom.id(extSlcId),
+            rows = tf.tbl.rows,
+            matchCase = tf.matchCase;
+
+        //custom select test
+        this.isCustom = tf.isCustomOptions(colIndex);
+
+        //custom selects text
+        var activeFlt;
+        if(isLinked && tf.activeFilterId){
+            activeFlt = tf.activeFilterId.split('_')[0];
+            activeFlt = activeFlt.split(tf.prfxFlt)[1];
+        }
+
+        /*** remember grid values ***/
+        var fltsValues = [], fltArr = [];
+        if(tf.rememberGridValues){
+            fltsValues =
+                tf.feature('store').getFilterValues(tf.fltsValuesCookie);
+            if(fltsValues && !Str.isEmpty(fltsValues.toString())){
+                if(this.isCustom){
+                    fltArr.push(fltsValues[colIndex]);
+                } else {
+                    fltArr = fltsValues[colIndex].split(' '+tf.orOperator+' ');
+                }
+            }
+        }
+
+        var excludedOpts = null,
+            filteredDataCol = null;
+        if(isLinked && tf.disableExcludedOptions){
+            excludedOpts = [];
+            filteredDataCol = [];
+        }
+
+        for(var 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;
+            }
+
+            var 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(var 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 ))) ))){
+                    var 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){
+                        var filteredCol = filteredDataCol[j];
+                        if(!filteredCol){
+                            filteredCol = tf.getFilteredDataCol(j);
+                        }
+                        if(!Arr.has(filteredCol, cell_string, matchCase) &&
+                            !Arr.has(
+                                excludedOpts, cell_string, matchCase) &&
+                            !this.isFirstLoad){
+                            excludedOpts.push(cell_data);
+                        }
+                    }
+                }//if colIndex==j
+            }//for j
+        }//for k
+
+        //Retrieves custom values
+        if(this.isCustom){
+            var customValues = tf.getCustomOptions(colIndex);
+            this.opts = customValues[0];
+            this.optsTxt = customValues[1];
+        }
+
+        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(); }
+            }
+        }
+
+        //asc sort
+        if(tf.sortNumAsc && tf.sortNumAsc.indexOf(colIndex) != -1){
+            try{
+                this.opts.sort( numSortAsc );
+                if(excludedOpts){
+                    excludedOpts.sort(numSortAsc);
+                }
+                if(this.isCustom){
+                    this.optsTxt.sort(numSortAsc);
+                }
+            } catch(e) {
+                this.opts.sort();
+                if(excludedOpts){
+                    excludedOpts.sort();
+                }
+                if(this.isCustom){
+                    this.optsTxt.sort();
+                }
+            }//in case there are alphanumeric values
+        }
+        //desc sort
+        if(tf.sortNumDesc && tf.sortNumDesc.indexOf(colIndex) != -1){
+            try{
+                this.opts.sort(numSortDesc);
+                if(excludedOpts){
+                    excludedOpts.sort(numSortDesc);
+                }
+                if(this.isCustom){
+                    this.optsTxt.sort(numSortDesc);
+                }
+            } catch(e) {
+                this.opts.sort();
+                if(excludedOpts){
+                    excludedOpts.sort();
+                }
+                if(this.isCustom){
+                    this.optsTxt.sort();
+                }
+            }//in case there are alphanumeric values
+        }
+
+        //populates drop-down
+        this.addOptions(
+            colIndex, slc, isLinked, excludedOpts, fltsValues, fltArr);
+    }
+
+    /**
+     * Add drop-down options
+     * @param {Number} colIndex     Column index
+     * @param {Object} slc          Select Dom element
+     * @param {Boolean} isLinked    Enable linked refresh behaviour
+     * @param {Array} excludedOpts  Array of excluded options
+     * @param {Array} fltsValues    Collection of persisted filter values
+     * @param {Array} fltArr        Collection of persisted filter values
+     */
+    addOptions(colIndex, slc, isLinked, excludedOpts, fltsValues, fltArr){
+        var tf = this.tf,
+            fillMethod = Str.lower(this.slcFillingMethod),
+            slcValue = slc.value;
+
+        slc.innerHTML = '';
+        slc = this.addFirstOption(slc);
+
+        for(var y=0; y<this.opts.length; y++){
+            if(this.opts[y]===''){
+                continue;
+            }
+            var val = this.opts[y]; //option value
+            var lbl = this.isCustom ? this.optsTxt[y] : val; //option text
+            var isDisabled = false;
+            if(isLinked && tf.disableExcludedOptions &&
+                Arr.has(
+                    excludedOpts,
+                    Str.matchCase(val, tf.matchCase),
+                    tf.matchCase
+                )){
+                isDisabled = true;
+            }
+
+            if(fillMethod === 'innerhtml'){
+                var slcAttr = '';
+                if(tf.loadFltOnDemand && slcValue===this.opts[y]){
+                    slcAttr = 'selected="selected"';
+                }
+                this.slcInnerHtml += '<option value="'+val+'" ' + slcAttr +
+                    (isDisabled ? 'disabled="disabled"' : '')+ '>' +
+                    lbl+'</option>';
+            } else {
+                var opt;
+                //fill select on demand
+                if(tf.loadFltOnDemand && slcValue===this.opts[y] &&
+                    tf.getFilterType(colIndex) === tf.fltTypeSlc){
+                    opt = Dom.createOpt(lbl, val, true);
+                } else {
+                    if(tf.getFilterType(colIndex) !== tf.fltTypeMulti){
+                        opt = Dom.createOpt(
+                            lbl,
+                            val,
+                            (fltsValues[colIndex]!==' ' &&
+                                val===fltsValues[colIndex]) ? true : false
+                        );
+                    } else {
+                        opt = Dom.createOpt(
+                            lbl,
+                            val,
+                            (Arr.has(fltArr,
+                                Str.matchCase(this.opts[y], tf.matchCase),
+                                tf.matchCase) ||
+                              fltArr.toString().indexOf(val)!== -1) ?
+                                true : false
+                        );
+                    }
+                }
+                if(isDisabled){
+                    opt.disabled = true;
+                }
+                slc.appendChild(opt);
+            }
+        }// for y
+
+        if(fillMethod === 'innerhtml'){
+            slc.innerHTML += this.slcInnerHtml;
+        }
+        slc.setAttribute('filled', '1');
+    }
+
+    /**
+     * Add drop-down header option
+     * @param {Object} slc Select DOM element
+     */
+    addFirstOption(slc){
+        var tf = this.tf,
+            fillMethod = Str.lower(this.slcFillingMethod);
+
+        if(fillMethod === 'innerhtml'){
+            this.slcInnerHtml += '<option value="">'+ tf.displayAllText +
+                '</option>';
+        }
+        else {
+            var opt0 = Dom.createOpt(
+                (!this.enableSlcResetFilter ? '' : tf.displayAllText),'');
+            if(!this.enableSlcResetFilter){
+                opt0.style.display = 'none';
+            }
+            slc.appendChild(opt0);
+            if(tf.enableEmptyOption){
+                var opt1 = Dom.createOpt(tf.emptyText, tf.emOperator);
+                slc.appendChild(opt1);
+            }
+            if(tf.enableNonEmptyOption){
+                var opt2 = Dom.createOpt(tf.nonEmptyText, tf.nmOperator);
+                slc.appendChild(opt2);
+            }
+        }
+        return slc;
+    }
+
+}
diff --git a/missing-sources/tablefilter/modules/feature.js b/missing-sources/tablefilter/modules/feature.js
new file mode 100644 (file)
index 0000000..f7861ce
--- /dev/null
@@ -0,0 +1,37 @@
+
+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.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;
+    }
+}
diff --git a/missing-sources/tablefilter/modules/gridLayout.js b/missing-sources/tablefilter/modules/gridLayout.js
new file mode 100644 (file)
index 0000000..959d674
--- /dev/null
@@ -0,0 +1,333 @@
+import {Feature} from './feature';
+import Dom from '../dom';
+import Types from '../types';
+import Event from '../event';
+
+export class GridLayout extends Feature{
+
+    /**
+     * Grid layout, table with fixed headers
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        super(tf, 'gridLayout');
+
+        var f = this.config;
+
+        //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';
+
+        this.gridColElms = [];
+
+        //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_';
+        //id of th containing column header if grid_layout true
+        this.prfxGridTh = 'tblHeadTh_';
+
+        this.sourceTblHtml = tf.tbl.outerHTML;
+    }
+
+    /**
+     * Generates a grid with fixed headers
+     */
+    init(){
+        var tf = this.tf;
+        var f = this.config;
+        var tbl = tf.tbl;
+
+        if(this.initialized){
+            return;
+        }
+
+        tf.isExternalFlt = true;
+
+        // default width of 100px if column widths not set
+        if(!tf.hasColWidths){
+            tf.colWidths = [];
+            for(var k=0; k<tf.nbCells; k++){
+                var 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);
+
+        var 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 = 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 = Dom.create('div',['id', this.prfxTblCont + tf.id]);
+        this.tblCont.className = this.gridContCssClass;
+        if(this.gridWidth){
+            if(this.gridWidth.indexOf('%') != -1){
+                console.log(this.gridWidth);
+                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);
+        var t = tbl.parentNode.removeChild(tbl);
+        this.tblCont.appendChild(t);
+
+        //In case table width is expressed in %
+        if(tbl.style.width === ''){
+            tbl.style.width = (tf._containsStr('%', tblW) ?
+                tbl.clientWidth : tblW) + 'px';
+        }
+
+        var d = this.tblCont.parentNode.removeChild(this.tblCont);
+        this.tblMainCont.appendChild(d);
+
+        //Headers table container: div wrapping headers table
+        this.headTblCont = Dom.create(
+            'div',['id', this.prfxHeadTblCont + tf.id]);
+        this.headTblCont.className = this.gridHeadContCssClass;
+        if(this.gridWidth){
+            if(this.gridWidth.indexOf('%') != -1){
+                console.log(this.gridWidth);
+                this.headTblCont.style.width = '100%';
+            } else {
+                this.headTblCont.style.width = this.gridWidth;
+            }
+        }
+
+        //Headers table
+        this.headTbl = Dom.create('table', ['id', this.prfxHeadTbl + tf.id]);
+        var tH = Dom.create('tHead');
+
+        //1st row should be headers row, ids are added if not set
+        //Those ids are used by the sort feature
+        var hRow = tbl.rows[this.gridHeadRowIndex];
+        var sortTriggers = [];
+        for(var n=0; n<tf.nbCells; n++){
+            var c = hRow.cells[n];
+            var thId = c.getAttribute('id');
+            if(!thId || thId===''){
+                thId = this.prfxGridTh+n+'_'+tf.id;
+                c.setAttribute('id', thId);
+            }
+            sortTriggers.push(thId);
+        }
+
+        //Filters row is created
+        var filtersRow = Dom.create('tr');
+        if(this.gridEnableFilters && tf.fltGrid){
+            tf.externalFltTgtIds = [];
+            for(var j=0; j<tf.nbCells; j++){
+                var fltTdId = tf.prfxFlt+j+ this.prfxGridFltTd +tf.id;
+                var cl = Dom.create(tf.fltCellTag, ['id', fltTdId]);
+                filtersRow.appendChild(cl);
+                tf.externalFltTgtIds[j] = fltTdId;
+            }
+        }
+        //Headers row are moved from content table to headers table
+        for(var i=0; i<this.gridHeadRows.length; i++){
+            var headRow = tbl.rows[this.gridHeadRows[0]];
+            tH.appendChild(headRow);
+        }
+        this.headTbl.appendChild(tH);
+        if(tf.filtersRowIndex === 0){
+            tH.insertBefore(filtersRow,hRow);
+        } else {
+            tH.appendChild(filtersRow);
+        }
+
+        this.headTblCont.appendChild(this.headTbl);
+        this.tblCont.parentNode.insertBefore(this.headTblCont, this.tblCont);
+
+        //THead needs to be removed in content table for sort feature
+        var thead = Dom.tag(tbl, 'thead');
+        if(thead.length>0){
+            tbl.removeChild(thead[0]);
+        }
+
+        //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(0, this.headTbl);
+
+        //Headers container width
+        // this.headTblCont.style.width = this.tblCont.clientWidth+'px';
+
+        tbl.style.width = '';
+        //
+        this.headTbl.style.width = tbl.clientWidth + 'px';
+        //
+
+        //scroll synchronisation
+        Event.add(this.tblCont, 'scroll', (evt)=> {
+            var elm = Event.target(evt);
+            var scrollLeft = elm.scrollLeft;
+            this.headTblCont.scrollLeft = scrollLeft;
+            //New pointerX calc taking into account scrollLeft
+            // if(!o.isPointerXOverwritten){
+            //     try{
+            //         o.Evt.pointerX = function(evt){
+            //             var e = evt || global.event;
+            //             var bdScrollLeft = tf_StandardBody().scrollLeft +
+            //                 scrollLeft;
+            //             return (e.pageX + scrollLeft) ||
+            //                 (e.clientX + bdScrollLeft);
+            //         };
+            //         o.isPointerXOverwritten = true;
+            //     } catch(err) {
+            //         o.isPointerXOverwritten = false;
+            //     }
+            // }
+        });
+
+        //Configure sort extension if any
+        var 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
+        var createColTags = function(){
+            for(var k=(tf.nbCells-1); k>=0; k--){
+                var 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(!this.tblHasColTag){
+            createColTags.call(this);
+        } else {
+            var cols = Dom.tag(tbl, 'col');
+            for(var 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]);
+            }
+        }
+
+        var 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;
+            }
+            var w = o.crWColsRow.cells[colIndex].style.width;
+            var col = o.gridColElms[colIndex];
+            col.style.width = w;
+
+            var thCW = o.crWColsRow.cells[colIndex].clientWidth;
+            var 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';
+        }
+
+        this.initialized = true;
+    }
+
+    /**
+     * Removes the grid layout
+     */
+    destroy(){
+        var tf = this.tf;
+        var tbl = tf.tbl;
+
+        if(!this.initialized){
+            return;
+        }
+        var t = tbl.parentNode.removeChild(tbl);
+        this.tblMainCont.parentNode.insertBefore(t, this.tblMainCont);
+        this.tblMainCont.parentNode.removeChild(this.tblMainCont);
+
+        this.tblMainCont = null;
+        this.headTblCont = null;
+        this.headTbl = null;
+        this.tblCont = null;
+
+        tbl.outerHTML = this.sourceTblHtml;
+        //needed to keep reference of table element
+        this.tf.tbl = Dom.id(tf.id); // ???
+
+        this.initialized = false;
+    }
+}
diff --git a/missing-sources/tablefilter/modules/help.js b/missing-sources/tablefilter/modules/help.js
new file mode 100644 (file)
index 0000000..7f0ae77
--- /dev/null
@@ -0,0 +1,155 @@
+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 = 'http://koalyptus.github.io/TableFilter/';
+
+export class Help extends Feature{
+
+    /**
+     * Help UI component
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        super(tf, 'help');
+
+        var f = this.config;
+
+        //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">' +
+            'Learn more</a><hr/>';
+        //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 content div
+        this.cont = null;
+        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>' +
+            '<div align="center" style="margin-top:8px;">' +
+            '<a href="javascript:void(0);" class="close">Close</a></div></div>';
+
+        //id prefix for help elements
+        this.prfxHelpSpan = 'helpSpan_';
+        //id prefix for help elements
+        this.prfxHelpDiv = 'helpDiv_';
+    }
+
+    init(){
+        if(this.initialized){
+            return;
+        }
+
+        var tf = this.tf;
+
+        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
+        if(!this.tgtId){
+            tf.setToolbar();
+        }
+        var targetEl = !this.tgtId ? tf.rDiv : Dom.id(this.tgtId);
+        targetEl.appendChild(helpspan);
+
+        var divContainer = !this.contTgtId ? helpspan : Dom.id(this.contTgtId);
+
+        if(!this.btnHtml){
+            divContainer.appendChild(helpdiv);
+            var helplink = Dom.create('a', ['href', 'javascript:void(0);']);
+            helplink.className = this.btnCssClass;
+            helplink.appendChild(Dom.text(this.btnText));
+            helpspan.appendChild(helplink);
+            Event.add(helplink, 'click', () => { this.toggle(); });
+        } else {
+            helpspan.innerHTML = this.btnHtml;
+            var helpEl = helpspan.firstChild;
+            Event.add(helpEl, 'click', () => { this.toggle(); });
+            divContainer.appendChild(helpdiv);
+        }
+
+        if(!this.instrHtml){
+            helpdiv.innerHTML = this.instrText;
+            helpdiv.className = this.contCssClass;
+            Event.add(helpdiv, 'dblclick', () => { this.toggle(); });
+        } else {
+            if(this.contTgtId){
+                divContainer.appendChild(helpdiv);
+            }
+            helpdiv.innerHTML = this.instrHtml;
+            if(!this.contTgtId){
+                helpdiv.className = this.contCssClass;
+                Event.add(helpdiv, 'dblclick', () => { this.toggle(); });
+            }
+        }
+        helpdiv.innerHTML += this.defaultHtml;
+        Event.add(helpdiv, 'click', () => { this.toggle(); });
+
+        this.cont = helpdiv;
+        this.btn = helpspan;
+        this.initialized = true;
+    }
+
+    /**
+     * Toggle help pop-up
+     */
+    toggle(){
+        // check only if explicitily set to false as in this case undefined
+        // signifies the help feature is enabled by default
+        if(this.enabled === false){
+            return;
+        }
+        var divDisplay = this.cont.style.display;
+        if(divDisplay === '' || divDisplay === 'none'){
+            this.cont.style.display = 'inline';
+        } else {
+            this.cont.style.display = 'none';
+        }
+    }
+
+    /**
+     * Remove help UI
+     */
+    destroy(){
+        if(!this.initialized){
+            return;
+        }
+        this.btn.parentNode.removeChild(this.btn);
+        this.btn = null;
+        if(!this.cont){
+            return;
+        }
+        this.cont.parentNode.removeChild(this.cont);
+        this.cont = null;
+
+        this.disable();
+        this.initialized = false;
+    }
+
+}
diff --git a/missing-sources/tablefilter/modules/highlightKeywords.js b/missing-sources/tablefilter/modules/highlightKeywords.js
new file mode 100644 (file)
index 0000000..ff4417c
--- /dev/null
@@ -0,0 +1,108 @@
+import Dom from '../dom';
+import Str from '../string';
+
+export class HighlightKeyword{
+
+    /**
+     * HighlightKeyword, highlight matched keyword
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf) {
+        var f = tf.config();
+        //defines css class for highlighting
+        this.highlightCssClass = f.highlight_css_class || 'keyword';
+        this.highlightedNodes = [];
+
+        this.tf = tf;
+    }
+
+    /**
+     * highlight occurences of searched term in passed node
+     * @param  {Node} node
+     * @param  {String} word     Searched term
+     * @param  {String} cssClass Css class name
+     */
+    highlight(node, word, cssClass){
+        // Iterate into this nodes childNodes
+        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){
+            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 = 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);
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes highlight to nodes matching passed string
+     * @param  {String} word
+     * @param  {String} cssClass Css class to remove
+     */
+    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 || !this.tf.searchArgs){
+            return;
+        }
+        for(var y=0; y<this.tf.searchArgs.length; y++){
+            this.unhighlight(
+                this.tf.searchArgs[y], this.highlightCssClass);
+        }
+        this.highlightedNodes = [];
+    }
+}
\ No newline at end of file
diff --git a/missing-sources/tablefilter/modules/loader.js b/missing-sources/tablefilter/modules/loader.js
new file mode 100644 (file)
index 0000000..fb7a583
--- /dev/null
@@ -0,0 +1,102 @@
+import {Feature} from './feature';
+import Dom from '../dom';
+import Types from '../types';
+
+var global = window;
+
+export class Loader extends Feature{
+
+    /**
+     * Loading message/spinner
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        super(tf, 'loader');
+
+        // TableFilter configuration
+        var f = this.config;
+
+        //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 = 200;
+        //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_';
+    }
+
+    init() {
+        if(this.initialized){
+            return;
+        }
+
+        var tf = this.tf;
+
+        var containerDiv = Dom.create('div', ['id', this.prfxLoader+tf.id]);
+        containerDiv.className = this.loaderCssClass;
+
+        var targetEl = !this.loaderTgtId ?
+            tf.tbl.parentNode : Dom.id(this.loaderTgtId);
+        if(!this.loaderTgtId){
+            targetEl.insertBefore(containerDiv, tf.tbl);
+        } else {
+            targetEl.appendChild(containerDiv);
+        }
+        this.loaderDiv = containerDiv;
+        if(!this.loaderHtml){
+            this.loaderDiv.appendChild(Dom.text(this.loaderText));
+        } else {
+            this.loaderDiv.innerHTML = this.loaderHtml;
+        }
+
+        this.show('none');
+        this.initialized = true;
+    }
+
+    show(p) {
+        if(!this.isEnabled() || this.loaderDiv.style.display === p){
+            return;
+        }
+
+        var displayLoader = () => {
+            if(!this.loaderDiv){
+                return;
+            }
+            if(this.onShowLoader && p !== 'none'){
+                this.onShowLoader.call(null, this);
+            }
+            this.loaderDiv.style.display = p;
+            if(this.onHideLoader && p === 'none'){
+                this.onHideLoader.call(null, this);
+            }
+        };
+
+        var t = p === 'none' ? this.loaderCloseDelay : 1;
+        global.setTimeout(displayLoader, t);
+    }
+
+    destroy(){
+        if(!this.initialized){
+            return;
+        }
+
+        this.loaderDiv.parentNode.removeChild(this.loaderDiv);
+        this.loaderDiv = null;
+
+        this.disable();
+        this.initialized = false;
+    }
+}
diff --git a/missing-sources/tablefilter/modules/paging.js b/missing-sources/tablefilter/modules/paging.js
new file mode 100644 (file)
index 0000000..a5473c8
--- /dev/null
@@ -0,0 +1,784 @@
+import {Feature} from './feature';
+import Dom from '../dom';
+import Types from '../types';
+import Str from '../string';
+import Event from '../event';
+
+export class Paging extends Feature{
+
+    /**
+     * Pagination component
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        super(tf, 'paging');
+
+        // Configuration object
+        var f = this.config;
+
+        //css class for paging buttons (previous,next,etc.)
+        this.btnPageCssClass = f.paging_btn_css_class || 'pgInp';
+        //stores paging select element
+        this.pagingSlc = null;
+        //results per page select element
+        this.resultsPerPageSlc = null;
+        //id of container element
+        this.pagingTgtId = f.paging_target_id || null;
+        //defines table paging length
+        this.pagingLength = !isNaN(f.paging_length) ? f.paging_length : 10;
+        //id of container element
+        this.resultsPerPageTgtId = f.results_per_page_target_id || null;
+        //css class for paging select element
+        this.pgSlcCssClass = f.paging_slc_css_class || 'pgSlc';
+        //css class for paging input element
+        this.pgInpCssClass = f.paging_inp_css_class || 'pgNbInp';
+        //stores results per page text and values
+        this.resultsPerPage = f.results_per_page || null;
+        //enables/disables results per page drop-down
+        this.hasResultsPerPage = Types.isArray(this.resultsPerPage);
+        //defines css class for results per page select
+        this.resultsSlcCssClass = f.results_slc_css_class || 'rspg';
+        //css class for label preceding results per page select
+        this.resultsSpanCssClass = f.results_span_css_class || 'rspgSpan';
+        //1st row index of current page
+        this.startPagingRow = 0;
+        //total nb of pages
+        this.nbPages = 0;
+        //current page nb
+        this.currentPageNb = 1;
+        //defines next page button text
+        this.btnNextPageText = f.btn_next_page_text || '>';
+        //defines previous page button text
+        this.btnPrevPageText = f.btn_prev_page_text || '<';
+        //defines last page button text
+        this.btnLastPageText = f.btn_last_page_text || '>|';
+        //defines first page button text
+        this.btnFirstPageText = f.btn_first_page_text || '|<';
+        //defines next page button html
+        this.btnNextPageHtml = f.btn_next_page_html ||
+            (!tf.enableIcons ? null :
+            '<input type="button" value="" class="'+this.btnPageCssClass +
+            ' nextPage" title="Next page" />');
+        //defines previous page button html
+        this.btnPrevPageHtml = f.btn_prev_page_html ||
+            (!tf.enableIcons ? null :
+            '<input type="button" value="" class="'+this.btnPageCssClass +
+            ' previousPage" title="Previous page" />');
+        //defines last page button html
+        this.btnFirstPageHtml = f.btn_first_page_html ||
+            (!tf.enableIcons ? null :
+            '<input type="button" value="" class="'+this.btnPageCssClass +
+            ' firstPage" title="First page" />');
+        //defines previous page button html
+        this.btnLastPageHtml = f.btn_last_page_html ||
+            (!tf.enableIcons ? null :
+            '<input type="button" value="" class="'+this.btnPageCssClass +
+            ' lastPage" title="Last page" />');
+        //defines text preceeding page selector drop-down
+        this.pageText = f.page_text || ' Page ';
+        //defines text after page selector drop-down
+        this.ofText = f.of_text || ' of ';
+        //css class for span containing tot nb of pages
+        this.nbPgSpanCssClass = f.nb_pages_css_class || 'nbpg';
+        //enables/disables paging buttons
+        this.hasPagingBtns = f.paging_btns===false ? false : true;
+        //defines previous page button html
+        this.pageSelectorType = f.page_selector_type || tf.fltTypeSlc;
+        //calls function before page is changed
+        this.onBeforeChangePage = Types.isFn(f.on_before_change_page) ?
+            f.on_before_change_page : null;
+        //calls function before page is changed
+        this.onAfterChangePage = Types.isFn(f.on_after_change_page) ?
+            f.on_after_change_page : null;
+
+        //pages select
+        this.prfxSlcPages = 'slcPages_';
+        //results per page select
+        this.prfxSlcResults = 'slcResults_';
+        //label preciding results per page select
+        this.prfxSlcResultsTxt = 'slcResultsTxt_';
+        //span containing next page button
+        this.prfxBtnNextSpan = 'btnNextSpan_';
+        //span containing previous page button
+        this.prfxBtnPrevSpan = 'btnPrevSpan_';
+        //span containing last page button
+        this.prfxBtnLastSpan = 'btnLastSpan_';
+        //span containing first page button
+        this.prfxBtnFirstSpan = 'btnFirstSpan_';
+        //next button
+        this.prfxBtnNext = 'btnNext_';
+        //previous button
+        this.prfxBtnPrev = 'btnPrev_';
+        //last button
+        this.prfxBtnLast = 'btnLast_';
+        //first button
+        this.prfxBtnFirst = 'btnFirst_';
+        //span for tot nb pages
+        this.prfxPgSpan = 'pgspan_';
+        //span preceding pages select (contains 'Page')
+        this.prfxPgBeforeSpan = 'pgbeforespan_';
+        //span following pages select (contains ' of ')
+        this.prfxPgAfterSpan = 'pgafterspan_';
+
+        var start_row = this.refRow;
+        var nrows = this.nbRows;
+        //calculates page nb
+        this.nbPages = Math.ceil((nrows-start_row)/this.pagingLength);
+
+        //Paging elements events
+        var o = this;
+        // Paging DOM events
+        this.evt = {
+            slcIndex(){
+                return (o.pageSelectorType===tf.fltTypeSlc) ?
+                    o.pagingSlc.options.selectedIndex :
+                    parseInt(o.pagingSlc.value, 10)-1;
+            },
+            nbOpts(){
+                return (o.pageSelectorType===tf.fltTypeSlc) ?
+                    parseInt(o.pagingSlc.options.length, 10)-1 :
+                    (o.nbPages-1);
+            },
+            next(){
+                var nextIndex = o.evt.slcIndex() < o.evt.nbOpts() ?
+                    o.evt.slcIndex()+1 : 0;
+                o.changePage(nextIndex);
+            },
+            prev(){
+                var prevIndex = o.evt.slcIndex()>0 ?
+                    o.evt.slcIndex()-1 : o.evt.nbOpts();
+                o.changePage(prevIndex);
+            },
+            last(){
+                o.changePage(o.evt.nbOpts());
+            },
+            first(){
+                o.changePage(0);
+            },
+            _detectKey(e){
+                var key = Event.keyCode(e);
+                if(key===13){
+                    if(tf.sorted){
+                        tf.filter();
+                        o.changePage(o.evt.slcIndex());
+                    } else{
+                        o.changePage();
+                    }
+                    this.blur();
+                }
+            },
+            slcPagesChange: null,
+            nextEvt: null,
+            prevEvt: null,
+            lastEvt: null,
+            firstEvt: null
+        };
+    }
+
+    /**
+     * Initialize DOM elements
+     */
+    init(){
+        var slcPages;
+        var tf = this.tf;
+        var evt = this.evt;
+
+        if(this.initialized){
+            return;
+        }
+
+        // Check resultsPerPage is in expected format and initialise the
+        // results per page component
+        if(this.hasResultsPerPage){
+            if(this.resultsPerPage.length<2){
+                this.hasResultsPerPage = false;
+            } else {
+                this.pagingLength = this.resultsPerPage[1][0];
+                this.setResultsPerPage();
+            }
+        }
+
+        evt.slcPagesChange = (event) => {
+            var slc = event.target;
+            this.changePage(slc.selectedIndex);
+        };
+
+        // Paging drop-down list selector
+        if(this.pageSelectorType === tf.fltTypeSlc){
+            slcPages = Dom.create(
+                tf.fltTypeSlc, ['id', this.prfxSlcPages+tf.id]);
+            slcPages.className = this.pgSlcCssClass;
+            Event.add(slcPages, 'change', evt.slcPagesChange);
+        }
+
+        // Paging input selector
+        if(this.pageSelectorType === tf.fltTypeInp){
+            slcPages = Dom.create(
+                tf.fltTypeInp,
+                ['id', this.prfxSlcPages+tf.id],
+                ['value', this.currentPageNb]
+            );
+            slcPages.className = this.pgInpCssClass;
+            Event.add(slcPages, 'keypress', evt._detectKey);
+        }
+
+        // btns containers
+        var btnNextSpan = Dom.create(
+            'span',['id', this.prfxBtnNextSpan+tf.id]);
+        var btnPrevSpan = Dom.create(
+            'span',['id', this.prfxBtnPrevSpan+tf.id]);
+        var btnLastSpan = Dom.create(
+            'span',['id', this.prfxBtnLastSpan+tf.id]);
+        var btnFirstSpan = Dom.create(
+            'span',['id', this.prfxBtnFirstSpan+tf.id]);
+
+        if(this.hasPagingBtns){
+            // Next button
+            if(!this.btnNextPageHtml){
+                var btn_next = Dom.create(
+                    tf.fltTypeInp,
+                    ['id', this.prfxBtnNext+tf.id],
+                    ['type', 'button'],
+                    ['value', this.btnNextPageText],
+                    ['title', 'Next']
+                );
+                btn_next.className = this.btnPageCssClass;
+                Event.add(btn_next, 'click', evt.next);
+                btnNextSpan.appendChild(btn_next);
+            } else {
+                btnNextSpan.innerHTML = this.btnNextPageHtml;
+                Event.add(btnNextSpan, 'click', evt.next);
+            }
+            // Previous button
+            if(!this.btnPrevPageHtml){
+                var btn_prev = Dom.create(
+                    tf.fltTypeInp,
+                    ['id', this.prfxBtnPrev+tf.id],
+                    ['type', 'button'],
+                    ['value', this.btnPrevPageText],
+                    ['title', 'Previous']
+                );
+                btn_prev.className = this.btnPageCssClass;
+                Event.add(btn_prev, 'click', evt.prev);
+                btnPrevSpan.appendChild(btn_prev);
+            } else {
+                btnPrevSpan.innerHTML = this.btnPrevPageHtml;
+                Event.add(btnPrevSpan, 'click', evt.prev);
+            }
+            // Last button
+            if(!this.btnLastPageHtml){
+                var btn_last = Dom.create(
+                    tf.fltTypeInp,
+                    ['id', this.prfxBtnLast+tf.id],
+                    ['type', 'button'],
+                    ['value', this.btnLastPageText],
+                    ['title', 'Last']
+                );
+                btn_last.className = this.btnPageCssClass;
+                Event.add(btn_last, 'click', evt.last);
+                btnLastSpan.appendChild(btn_last);
+            } else {
+                btnLastSpan.innerHTML = this.btnLastPageHtml;
+                Event.add(btnLastSpan, 'click', evt.last);
+            }
+            // First button
+            if(!this.btnFirstPageHtml){
+                var btn_first = Dom.create(
+                    tf.fltTypeInp,
+                    ['id', this.prfxBtnFirst+tf.id],
+                    ['type', 'button'],
+                    ['value', this.btnFirstPageText],
+                    ['title', 'First']
+                );
+                btn_first.className = this.btnPageCssClass;
+                Event.add(btn_first, 'click', evt.first);
+                btnFirstSpan.appendChild(btn_first);
+            } else {
+                btnFirstSpan.innerHTML = this.btnFirstPageHtml;
+                Event.add(btnFirstSpan, 'click', evt.first);
+            }
+        }
+
+        // paging elements (buttons+drop-down list) are added to defined element
+        if(!this.pagingTgtId){
+            tf.setToolbar();
+        }
+        var targetEl = !this.pagingTgtId ? tf.mDiv : Dom.id(this.pagingTgtId);
+        targetEl.appendChild(btnFirstSpan);
+        targetEl.appendChild(btnPrevSpan);
+
+        var pgBeforeSpan = Dom.create(
+            'span',['id', this.prfxPgBeforeSpan+tf.id] );
+        pgBeforeSpan.appendChild( Dom.text(this.pageText) );
+        pgBeforeSpan.className = this.nbPgSpanCssClass;
+        targetEl.appendChild(pgBeforeSpan);
+        targetEl.appendChild(slcPages);
+        var pgAfterSpan = Dom.create(
+            'span',['id', this.prfxPgAfterSpan+tf.id]);
+        pgAfterSpan.appendChild( Dom.text(this.ofText) );
+        pgAfterSpan.className = this.nbPgSpanCssClass;
+        targetEl.appendChild(pgAfterSpan);
+        var pgspan = Dom.create( 'span',['id', this.prfxPgSpan+tf.id] );
+        pgspan.className = this.nbPgSpanCssClass;
+        pgspan.appendChild( Dom.text(' '+this.nbPages+' ') );
+        targetEl.appendChild(pgspan);
+        targetEl.appendChild(btnNextSpan);
+        targetEl.appendChild(btnLastSpan);
+        this.pagingSlc = Dom.id(this.prfxSlcPages+tf.id);
+
+        if(!tf.rememberGridValues){
+            this.setPagingInfo();
+        }
+        if(!tf.fltGrid){
+            tf.validateAllRows();
+            this.setPagingInfo(tf.validRowsIndex);
+        }
+
+        this.initialized = true;
+    }
+
+    /**
+     * Reset paging when filters are already instantiated
+     * @param {Boolean} filterTable Execute filtering once paging instanciated
+     */
+    reset(filterTable=false){
+        var tf = this.tf;
+        if(!tf.hasGrid() || this.isEnabled()){
+            return;
+        }
+        this.enable();
+        this.init();
+        tf.resetValues();
+        if(filterTable){
+            tf.filter();
+        }
+    }
+
+    /**
+     * Calculate number of pages based on valid rows
+     * Refresh paging select according to number of pages
+     * @param {Array} validRows Collection of valid rows
+     */
+    setPagingInfo(validRows=[]){
+        var tf = this.tf;
+        var rows = tf.tbl.rows;
+        var mdiv = !this.pagingTgtId ? tf.mDiv : Dom.id(this.pagingTgtId);
+        var pgspan = Dom.id(this.prfxPgSpan+tf.id);
+
+        //store valid rows indexes
+        tf.validRowsIndex = validRows;
+
+        if(validRows.length === 0){
+            //counts rows to be grouped
+            for(var j=tf.refRow; j<tf.nbRows; j++){
+                var row = rows[j];
+                if(!row){
+                    continue;
+                }
+
+                var isRowValid = row.getAttribute('validRow');
+                if(Types.isNull(isRowValid) || Boolean(isRowValid==='true')){
+                    tf.validRowsIndex.push(j);
+                }
+            }
+        }
+
+        //calculate nb of pages
+        this.nbPages = Math.ceil(tf.validRowsIndex.length/this.pagingLength);
+        //refresh page nb span
+        pgspan.innerHTML = this.nbPages;
+        //select clearing shortcut
+        if(this.pageSelectorType === tf.fltTypeSlc){
+            this.pagingSlc.innerHTML = '';
+        }
+
+        if(this.nbPages>0){
+            mdiv.style.visibility = 'visible';
+            if(this.pageSelectorType === tf.fltTypeSlc){
+                for(var z=0; z<this.nbPages; z++){
+                    var opt = Dom.createOpt(z+1, z*this.pagingLength, false);
+                    this.pagingSlc.options[z] = opt;
+                }
+            } else{
+                //input type
+                this.pagingSlc.value = this.currentPageNb;
+            }
+
+        } else {
+            /*** if no results paging select and buttons are hidden ***/
+            mdiv.style.visibility = 'hidden';
+        }
+        this.groupByPage(tf.validRowsIndex);
+    }
+
+    /**
+     * Group table rows by page and display valid rows
+     * @param  {Array} validRows Collection of valid rows
+     */
+    groupByPage(validRows){
+        var tf = this.tf;
+        var alternateRows =  tf.feature('alternateRows');
+        var rows = tf.tbl.rows;
+        var endPagingRow = parseInt(this.startPagingRow, 10) +
+            parseInt(this.pagingLength, 10);
+
+        //store valid rows indexes
+        if(validRows){
+            tf.validRowsIndex = validRows;
+        }
+
+        //this loop shows valid rows of current page
+        for(var h=0, len=tf.validRowsIndex.length; h<len; h++){
+            var validRowIdx = tf.validRowsIndex[h];
+            var r = rows[validRowIdx];
+            var isRowValid = r.getAttribute('validRow');
+
+            if(h>=this.startPagingRow && h<endPagingRow){
+                if(Types.isNull(isRowValid) || Boolean(isRowValid==='true')){
+                    r.style.display = '';
+                }
+                if(tf.alternateRows && alternateRows){
+                    alternateRows.setRowBg(validRowIdx, h);
+                }
+            } else {
+                r.style.display = 'none';
+                if(tf.alternateRows && alternateRows){
+                    alternateRows.removeRowBg(validRowIdx);
+                }
+            }
+        }
+
+        tf.nbVisibleRows = tf.validRowsIndex.length;
+        //re-applies filter behaviours after filtering process
+        tf.applyProps();
+    }
+
+    /**
+     * Return the current page number
+     * @return {Number} Page number
+     */
+    getPage(){
+        return this.currentPageNb;
+    }
+
+    /**
+     * Show page based on passed param value (string or number):
+     * @param {String} or {Number} cmd possible string values: 'next',
+     * 'previous', 'last', 'first' or page number as per param
+     */
+    setPage(cmd){
+        var tf = this.tf;
+        if(!tf.hasGrid() || !this.isEnabled()){
+            return;
+        }
+        var btnEvt = this.evt,
+            cmdtype = typeof cmd;
+        if(cmdtype==='string'){
+            switch(Str.lower(cmd)){
+                case 'next':
+                    btnEvt.next();
+                break;
+                case 'previous':
+                    btnEvt.prev();
+                break;
+                case 'last':
+                    btnEvt.last();
+                break;
+                case 'first':
+                    btnEvt.first();
+                break;
+                default:
+                    btnEvt.next();
+                break;
+            }
+        }
+        else if(cmdtype==='number'){
+            this.changePage(cmd-1);
+        }
+    }
+
+    /**
+     * Generates UI elements for the number of results per page drop-down
+     */
+    setResultsPerPage(){
+        var tf = this.tf;
+        var evt = this.evt;
+
+        if(!tf.hasGrid() && !tf.isFirstLoad){
+            return;
+        }
+        if(this.resultsPerPageSlc || !this.resultsPerPage){
+            return;
+        }
+
+        evt.slcResultsChange = (ev) => {
+            this.changeResultsPerPage();
+            ev.target.blur();
+        };
+
+        var slcR = Dom.create(
+            tf.fltTypeSlc, ['id', this.prfxSlcResults+tf.id]);
+        slcR.className = this.resultsSlcCssClass;
+        var slcRText = this.resultsPerPage[0],
+            slcROpts = this.resultsPerPage[1];
+        var slcRSpan = Dom.create(
+            'span',['id', this.prfxSlcResultsTxt+tf.id]);
+        slcRSpan.className = this.resultsSpanCssClass;
+
+        // results per page select is added to external element
+        if(!this.resultsPerPageTgtId){
+            tf.setToolbar();
+        }
+        var targetEl = !this.resultsPerPageTgtId ?
+            tf.rDiv : Dom.id(this.resultsPerPageTgtId);
+        slcRSpan.appendChild(Dom.text(slcRText));
+
+        var help = tf.feature('help');
+        if(help && help.btn){
+            help.btn.parentNode.insertBefore(slcRSpan, help.btn);
+            help.btn.parentNode.insertBefore(slcR, help.btn);
+        } else {
+            targetEl.appendChild(slcRSpan);
+            targetEl.appendChild(slcR);
+        }
+
+        for(var r=0; r<slcROpts.length; r++){
+            var currOpt = new Option(slcROpts[r], slcROpts[r], false, false);
+            slcR.options[r] = currOpt;
+        }
+        Event.add(slcR, 'change', evt.slcResultsChange);
+        this.resultsPerPageSlc = slcR;
+    }
+
+    /**
+     * Remove number of results per page UI elements
+     */
+    removeResultsPerPage(){
+        var tf = this.tf;
+        if(!tf.hasGrid() || !this.resultsPerPageSlc || !this.resultsPerPage){
+            return;
+        }
+        var slcR = this.resultsPerPageSlc,
+            slcRSpan = Dom.id(this.prfxSlcResultsTxt+tf.id);
+        if(slcR){
+            slcR.parentNode.removeChild(slcR);
+        }
+        if(slcRSpan){
+            slcRSpan.parentNode.removeChild(slcRSpan);
+        }
+        this.resultsPerPageSlc = null;
+    }
+
+    /**
+     * Change the page asynchronously according to passed index
+     * @param  {Number} index Index of the page (0-n)
+     */
+    changePage(index){
+        var tf = this.tf;
+        var evt = tf.Evt;
+        tf.EvtManager(evt.name.changepage, { pgIndex:index });
+    }
+
+    /**
+     * Change rows asynchronously according to page results
+     */
+    changeResultsPerPage(){
+        var tf = this.tf;
+        var evt = tf.Evt;
+        tf.EvtManager(evt.name.changeresultsperpage);
+    }
+
+    /**
+     * Re-set asynchronously page nb at page re-load
+     */
+    resetPage(){
+        var tf = this.tf;
+        var evt = tf.Evt;
+        tf.EvtManager(evt.name.resetpage);
+    }
+
+    /**
+     * Re-set asynchronously page length at page re-load
+     */
+    resetPageLength(){
+        var tf = this.tf;
+        var evt = tf.Evt;
+        tf.EvtManager(evt.name.resetpagelength);
+    }
+
+    /**
+     * Change the page according to passed index
+     * @param  {Number} index Index of the page (0-n)
+     */
+    _changePage(index){
+        var tf = this.tf;
+
+        if(!this.isEnabled()){
+            return;
+        }
+        if(index === null){
+            index = this.pageSelectorType===tf.fltTypeSlc ?
+                this.pagingSlc.options.selectedIndex : (this.pagingSlc.value-1);
+        }
+        if( index>=0 && index<=(this.nbPages-1) ){
+            if(this.onBeforeChangePage){
+                this.onBeforeChangePage.call(null, this, index);
+            }
+            this.currentPageNb = parseInt(index, 10)+1;
+            if(this.pageSelectorType===tf.fltTypeSlc){
+                this.pagingSlc.options[index].selected = true;
+            } else {
+                this.pagingSlc.value = this.currentPageNb;
+            }
+
+            if(tf.rememberPageNb){
+                tf.feature('store').savePageNb(tf.pgNbCookie);
+            }
+            this.startPagingRow = (this.pageSelectorType===tf.fltTypeSlc) ?
+                this.pagingSlc.value : (index*this.pagingLength);
+
+            this.groupByPage();
+
+            if(this.onAfterChangePage){
+                this.onAfterChangePage.call(null, this, index);
+            }
+        }
+    }
+
+    /**
+     * Change rows according to page results drop-down
+     * TODO: accept a parameter setting the results per page length
+     */
+    _changeResultsPerPage(){
+        var tf = this.tf;
+
+        if(!this.isEnabled()){
+            return;
+        }
+        var slcR = this.resultsPerPageSlc;
+        var slcPagesSelIndex = (this.pageSelectorType===tf.fltTypeSlc) ?
+                this.pagingSlc.selectedIndex :
+                parseInt(this.pagingSlc.value-1, 10);
+        this.pagingLength = parseInt(slcR.options[slcR.selectedIndex].value,10);
+        this.startPagingRow = this.pagingLength*slcPagesSelIndex;
+
+        if(!isNaN(this.pagingLength)){
+            if(this.startPagingRow >= tf.nbFilterableRows){
+                this.startPagingRow = (tf.nbFilterableRows-this.pagingLength);
+            }
+            this.setPagingInfo();
+
+            if(this.pageSelectorType===tf.fltTypeSlc){
+                var slcIndex =
+                    (this.pagingSlc.options.length-1<=slcPagesSelIndex ) ?
+                    (this.pagingSlc.options.length-1) : slcPagesSelIndex;
+                this.pagingSlc.options[slcIndex].selected = true;
+            }
+            if(tf.rememberPageLen){
+                tf.feature('store').savePageLength(tf.pgLenCookie);
+            }
+        }
+    }
+
+    /**
+     * Re-set page nb at page re-load
+     */
+    _resetPage(name){
+        var tf = this.tf;
+        var pgnb = tf.feature('store').getPageNb(name);
+        if(pgnb!==''){
+            this.changePage((pgnb-1));
+        }
+    }
+
+    /**
+     * Re-set page length value at page re-load
+     */
+    _resetPageLength(name){
+        var tf = this.tf;
+        if(!this.isEnabled()){
+            return;
+        }
+        var pglenIndex = tf.feature('store').getPageLength(name);
+
+        if(pglenIndex!==''){
+            this.resultsPerPageSlc.options[pglenIndex].selected = true;
+            this.changeResultsPerPage();
+        }
+    }
+
+    /**
+     * Remove paging feature
+     */
+    destroy(){
+        var tf = this.tf;
+
+        if(!this.initialized){
+            return;
+        }
+        // btns containers
+        var btnNextSpan = Dom.id(this.prfxBtnNextSpan+tf.id);
+        var btnPrevSpan = Dom.id(this.prfxBtnPrevSpan+tf.id);
+        var btnLastSpan = Dom.id(this.prfxBtnLastSpan+tf.id);
+        var btnFirstSpan = Dom.id(this.prfxBtnFirstSpan+tf.id);
+        //span containing 'Page' text
+        var pgBeforeSpan = Dom.id(this.prfxPgBeforeSpan+tf.id);
+        //span containing 'of' text
+        var pgAfterSpan = Dom.id(this.prfxPgAfterSpan+tf.id);
+        //span containing nb of pages
+        var pgspan = Dom.id(this.prfxPgSpan+tf.id);
+
+        var evt = this.evt;
+
+        if(this.pagingSlc){
+            if(this.pageSelectorType === tf.fltTypeSlc){
+                Event.remove(this.pagingSlc, 'change', evt.slcPagesChange);
+            }
+            else if(this.pageSelectorType === tf.fltTypeInp){
+                Event.remove(this.pagingSlc, 'keypress', evt._detectKey);
+            }
+            this.pagingSlc.parentNode.removeChild(this.pagingSlc);
+        }
+
+        if(btnNextSpan){
+            Event.remove(btnNextSpan, 'click', evt.next);
+            btnNextSpan.parentNode.removeChild(btnNextSpan);
+        }
+
+        if(btnPrevSpan){
+            Event.remove(btnPrevSpan, 'click', evt.prev);
+            btnPrevSpan.parentNode.removeChild(btnPrevSpan);
+        }
+
+        if(btnLastSpan){
+            Event.remove(btnLastSpan, 'click', evt.last);
+            btnLastSpan.parentNode.removeChild(btnLastSpan);
+        }
+
+        if(btnFirstSpan){
+            Event.remove(btnFirstSpan, 'click', evt.first);
+            btnFirstSpan.parentNode.removeChild(btnFirstSpan);
+        }
+
+        if(pgBeforeSpan){
+            pgBeforeSpan.parentNode.removeChild(pgBeforeSpan);
+        }
+
+        if(pgAfterSpan){
+            pgAfterSpan.parentNode.removeChild(pgAfterSpan);
+        }
+
+        if(pgspan){
+            pgspan.parentNode.removeChild(pgspan);
+        }
+
+        if(this.hasResultsPerPage){
+            this.removeResultsPerPage();
+        }
+
+        this.pagingSlc = null;
+        this.nbPages = 0;
+        this.disable();
+        this.initialized = false;
+    }
+}
diff --git a/missing-sources/tablefilter/modules/popupFilter.js b/missing-sources/tablefilter/modules/popupFilter.js
new file mode 100644 (file)
index 0000000..f2de5b2
--- /dev/null
@@ -0,0 +1,253 @@
+import {Feature} from './feature';
+import Types from '../types';
+import Dom from '../dom';
+import Event from '../event';
+
+export class PopupFilter extends Feature{
+
+    /**
+     * Pop-up filter component
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        super(tf, 'popupFilters');
+
+        // Configuration object
+        var f = this.config;
+
+        // Enable external filters behaviour
+        tf.isExternalFlt = true;
+        tf.externalFltTgtIds = [];
+
+        //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;
+
+        //stores filters spans
+        this.popUpFltSpans = [];
+        //stores filters icons
+        this.popUpFltImgs = [];
+        //stores filters containers
+        this.popUpFltElms = this.popUpFltElmCache || [];
+        this.popUpFltAdjustToContainer = true;
+
+        //id prefix for pop-up filter span
+        this.prfxPopUpSpan = 'popUpSpan_';
+        //id prefix for pop-up div containing filter
+        this.prfxPopUpDiv = 'popUpDiv_';
+    }
+
+    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.popUpFltAdjustToContainer){
+            var popUpDiv = this.popUpFltElms[colIndex],
+                header = this.tf.getHeaderElement(colIndex),
+                headerWidth = header.clientWidth * 0.95;
+            popUpDiv.style.width = parseInt(headerWidth, 10)  + 'px';
+        }
+        Event.cancel(evt);
+        Event.stop(evt);
+    }
+
+    /**
+     * Initialize DOM elements
+     */
+    init(){
+        if(this.initialized){
+            return;
+        }
+
+        var tf = this.tf;
+        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;
+        }
+
+        this.initialized = true;
+    }
+
+    /**
+     * Reset previously destroyed feature
+     */
+    reset(){
+        this.enable();
+        this.init();
+        this.buildAll();
+    }
+
+    /**
+     * Build all pop-up filters elements
+     */
+    buildAll(){
+        for(var i=0; i<this.popUpFltElmCache.length; i++){
+            this.build(i, this.popUpFltElmCache[i]);
+        }
+    }
+
+    /**
+     * Build a specified pop-up filter elements
+     * @param  {Number} colIndex Column index
+     * @param  {Object} div      Optional container DOM element
+     */
+    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;
+    }
+
+    /**
+     * Toogle visibility of specified filter
+     * @param  {Number} colIndex Column index
+     */
+    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 {
+            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);
+            }
+        }
+    }
+
+    /**
+     * Close all filters excepted for the specified one if any
+     * @param  {Number} exceptIdx Column index of the filter to not close
+     */
+    closeAll(exceptIdx){
+        for(var i=0; i<this.popUpFltElms.length; i++){
+            if(i === exceptIdx){
+                continue;
+            }
+            var popUpFltElm = this.popUpFltElms[i];
+            if(popUpFltElm){
+                popUpFltElm.style.display = 'none';
+            }
+        }
+    }
+
+    /**
+     * Build all the icons representing the pop-up filters
+     */
+    buildIcons(){
+        for(var i=0; i<this.popUpFltImgs.length; i++){
+            this.buildIcon(i, false);
+        }
+    }
+
+    /**
+     * Build specified icon
+     * @param  {Number} colIndex Column index
+     * @param  {Boolean} active   Apply active state
+     */
+    buildIcon(colIndex, active){
+        if(this.popUpFltImgs[colIndex]){
+            this.popUpFltImgs[colIndex].src = active ?
+                this.popUpImgFltActive : this.popUpImgFlt;
+        }
+    }
+
+    /**
+     * Remove pop-up filters
+     */
+    destroy(){
+        if(!this.initialized){
+            return;
+        }
+
+        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){
+                popUpFltElm.parentNode.removeChild(popUpFltElm);
+                this.popUpFltElmCache[i] = popUpFltElm;
+            }
+            popUpFltElm = null;
+            if(popUpFltSpan){
+                popUpFltSpan.parentNode.removeChild(popUpFltSpan);
+            }
+            popUpFltSpan = null;
+            if(popUpFltImg){
+                popUpFltImg.parentNode.removeChild(popUpFltImg);
+            }
+            popUpFltImg = null;
+        }
+        this.popUpFltElms = [];
+        this.popUpFltSpans = [];
+        this.popUpFltImgs = [];
+
+        this.disable();
+        this.initialized = false;
+    }
+
+}
diff --git a/missing-sources/tablefilter/modules/rowsCounter.js b/missing-sources/tablefilter/modules/rowsCounter.js
new file mode 100644 (file)
index 0000000..c110cd9
--- /dev/null
@@ -0,0 +1,139 @@
+import {Feature} from './feature';
+import Dom from '../dom';
+import Types from '../types';
+
+export class RowsCounter extends Feature{
+
+    /**
+     * Rows counter
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        super(tf, 'rowsCounter');
+
+        // TableFilter configuration
+        var f = this.config;
+
+        //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;
+    }
+
+    init(){
+        if(this.initialized){
+            return;
+        }
+
+        var tf = this.tf;
+
+        //rows counter container
+        var countDiv = Dom.create('div', ['id', this.prfxCounter+tf.id]);
+        countDiv.className = this.totRowsCssClass;
+        //rows counter label
+        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
+        if(!this.rowsCounterTgtId){
+            tf.setToolbar();
+        }
+        var targetEl = !this.rowsCounterTgtId ?
+                tf.lDiv : Dom.id( this.rowsCounterTgtId );
+
+        //default container: 'lDiv'
+        if(!this.rowsCounterTgtId){
+            countDiv.appendChild(countText);
+            countDiv.appendChild(countSpan);
+            targetEl.appendChild(countDiv);
+        }
+        else{
+            //custom container, no need to append statusDiv
+            targetEl.appendChild(countText);
+            targetEl.appendChild(countSpan);
+        }
+        this.rowsCounterDiv = countDiv;
+        this.rowsCounterSpan = countSpan;
+
+        this.initialized = true;
+        this.refresh();
+    }
+
+    refresh(p){
+        if(!this.rowsCounterSpan){
+            return;
+        }
+
+        var tf = this.tf;
+
+        if(this.onBeforeRefreshCounter){
+            this.onBeforeRefreshCounter.call(null, tf, this.rowsCounterSpan);
+        }
+
+        var totTxt;
+        if(!tf.paging){
+            if(p && p !== ''){
+                totTxt = p;
+            } else{
+                totTxt = tf.nbFilterableRows - tf.nbHiddenRows;
+            }
+        } else {
+            var paging = tf.feature('paging');
+            if(paging){
+                //paging start row
+                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.rowsCounterSpan.innerHTML = totTxt;
+        if(this.onAfterRefreshCounter){
+            this.onAfterRefreshCounter.call(
+                null, tf, this.rowsCounterSpan, totTxt);
+        }
+    }
+
+    destroy(){
+        if(!this.initialized){
+            return;
+        }
+
+        if(!this.rowsCounterTgtId && this.rowsCounterDiv){
+            this.rowsCounterDiv.parentNode.removeChild(this.rowsCounterDiv);
+        } else {
+            Dom.id(this.rowsCounterTgtId).innerHTML = '';
+        }
+        this.rowsCounterSpan = null;
+        this.rowsCounterDiv = null;
+
+        this.disable();
+        this.initialized = false;
+    }
+}
diff --git a/missing-sources/tablefilter/modules/statusBar.js b/missing-sources/tablefilter/modules/statusBar.js
new file mode 100644 (file)
index 0000000..44bac19
--- /dev/null
@@ -0,0 +1,125 @@
+import {Feature} from './feature';
+import Dom from '../dom';
+import Types from '../types';
+
+var global = window;
+
+export class StatusBar extends Feature{
+
+    /**
+     * Status bar UI component
+     * @param {Object} tf TableFilter instance
+     */
+    constructor(tf){
+        super(tf, 'statusBar');
+
+        // Configuration object
+        var f = this.config;
+
+        //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;
+
+        //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;
+
+        // status bar div
+        this.prfxStatus = 'status_';
+        // status bar label
+        this.prfxStatusSpan = 'statusSpan_';
+        // text preceding status bar label
+        this.prfxStatusTxt = 'statusText_';
+    }
+
+    init(){
+        if(this.initialized){
+            return;
+        }
+
+        var tf = this.tf;
+
+        //status bar container
+        var statusDiv = Dom.create('div', ['id', this.prfxStatus+tf.id]);
+        statusDiv.className = this.statusBarCssClass;
+
+        //status bar label
+        var statusSpan = Dom.create('span', ['id', this.prfxStatusSpan+tf.id]);
+        //preceding text
+        var statusSpanText = Dom.create('span',
+            ['id', this.prfxStatusTxt+tf.id]);
+        statusSpanText.appendChild(Dom.text(this.statusBarText));
+
+        // target element container
+        if(!this.statusBarTgtId){
+            tf.setToolbar();
+        }
+        var targetEl = (!this.statusBarTgtId) ?
+                tf.lDiv : Dom.id(this.statusBarTgtId);
+
+        //default container: 'lDiv'
+        if(!this.statusBarTgtId){
+            statusDiv.appendChild(statusSpanText);
+            statusDiv.appendChild(statusSpan);
+            targetEl.appendChild(statusDiv);
+        } else {
+            // custom container, no need to append statusDiv
+            targetEl.appendChild(statusSpanText);
+            targetEl.appendChild(statusSpan);
+        }
+
+        this.statusBarDiv = statusDiv;
+        this.statusBarSpan = statusSpan;
+        this.statusBarSpanText = statusSpanText;
+
+        this.initialized = true;
+    }
+
+    message(t=''){
+        if(!this.isEnabled()){
+            return;
+        }
+
+        if(this.onBeforeShowMsg){
+            this.onBeforeShowMsg.call(null, this.tf, t);
+        }
+
+        var d = t==='' ? this.statusBarCloseDelay : 1;
+        global.setTimeout(() => {
+            this.statusBarSpan.innerHTML = t;
+            if(this.onAfterShowMsg){
+                this.onAfterShowMsg.call(null, this.tf, t);
+            }
+        }, d);
+    }
+
+    destroy(){
+        if(!this.initialized){
+            return;
+        }
+
+        this.statusBarDiv.innerHTML = '';
+        this.statusBarDiv.parentNode.removeChild(this.statusBarDiv);
+        this.statusBarSpan = null;
+        this.statusBarSpanText = null;
+        this.statusBarDiv = null;
+
+        this.disable();
+        this.initialized = false;
+    }
+
+}
diff --git a/missing-sources/tablefilter/modules/store.js b/missing-sources/tablefilter/modules/store.js
new file mode 100644 (file)
index 0000000..d24757d
--- /dev/null
@@ -0,0 +1,100 @@
+import Cookie from '../cookie';
+
+export class Store{
+
+    /**
+     * Store, persistence manager
+     * @param {Object} tf TableFilter instance
+     *
+     * TODO: use localStorage and fallback to cookie persistence
+     */
+    constructor(tf) {
+        var f = tf.config();
+
+        this.duration = !isNaN(f.set_cookie_duration) ?
+            parseInt(f.set_cookie_duration, 10) : 100000;
+
+        this.tf = tf;
+    }
+
+    /**
+     * Store filters' values in cookie
+     * @param {String} cookie name
+     */
+    saveFilterValues(name){
+        var tf = this.tf;
+        var fltValues = [];
+        //store filters' values
+        for(var i=0; i<tf.fltIds.length; i++){
+            var value = tf.getFilterValue(i);
+            if (value === ''){
+                value = ' ';
+            }
+            fltValues.push(value);
+        }
+        //adds array size
+        fltValues.push(tf.fltIds.length);
+
+        //writes cookie
+        Cookie.write(
+            name,
+            fltValues.join(tf.separator),
+            this.duration
+        );
+    }
+
+    /**
+     * Retrieve filters' values from cookie
+     * @param {String} cookie name
+     * @return {Array}
+     */
+    getFilterValues(name){
+        var flts = Cookie.read(name);
+        var rgx = new RegExp(this.tf.separator, 'g');
+        // filters' values array
+        return flts.split(rgx);
+    }
+
+    /**
+     * Store page number in cookie
+     * @param {String} cookie name
+     */
+    savePageNb(name){
+        Cookie.write(
+            name,
+            this.tf.feature('paging').currentPageNb,
+            this.duration
+        );
+    }
+
+    /**
+     * Retrieve page number from cookie
+     * @param {String} cookie name
+     * @return {String}
+     */
+    getPageNb(name){
+        return Cookie.read(name);
+    }
+
+    /**
+     * Store page length in cookie
+     * @param {String} cookie name
+     */
+    savePageLength(name){
+        Cookie.write(
+            name,
+            this.tf.feature('paging').resultsPerPageSlc.selectedIndex,
+            this.duration
+        );
+    }
+
+    /**
+     * Retrieve page length from cookie
+     * @param {String} cookie name
+     * @return {String}
+     */
+    getPageLength(name){
+        return Cookie.read(name);
+    }
+
+}
diff --git a/missing-sources/tablefilter/sort.js b/missing-sources/tablefilter/sort.js
new file mode 100644 (file)
index 0000000..fc7cb79
--- /dev/null
@@ -0,0 +1,9 @@
+import Str from './string';
+
+export default {
+   ignoreCase(a, b){
+        let x = Str.lower(a);
+        let y = Str.lower(b);
+        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+    }
+};
diff --git a/missing-sources/tablefilter/string.js b/missing-sources/tablefilter/string.js
new file mode 100644 (file)
index 0000000..452de37
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * String utilities
+ */
+
+export default {
+
+    lower(text){
+        return text.toLowerCase();
+    },
+
+    upper(text){
+        return text.toUpperCase();
+    },
+
+    trim(text){
+        if (text.trim){
+            return text.trim();
+        }
+        return text.replace(/^\s*|\s*$/g, '');
+    },
+
+    isEmpty(text){
+        return this.trim(text) === '';
+    },
+
+    rgxEsc(text){
+        let chars = /[-\/\\^$*+?.()|[\]{}]/g;
+        let escMatch = '\\$&';
+        return String(text).replace(chars, escMatch);
+    },
+
+    matchCase(text, mc){
+        if(!mc){
+            return this.lower(text);
+        }
+        return text;
+    }
+
+};
diff --git a/missing-sources/tablefilter/tablefilter.js b/missing-sources/tablefilter/tablefilter.js
new file mode 100644 (file)
index 0000000..bdd323c
--- /dev/null
@@ -0,0 +1,2762 @@
+import Event from './event';
+import Dom from './dom';
+import Str from './string';
+import Cookie from './cookie';
+import Types from './types';
+import Arr from './array';
+import DateHelper from './date';
+import Helpers from './helpers';
+
+// Features
+import {Store} from './modules/store';
+import {GridLayout} from './modules/gridLayout';
+import {Loader} from './modules/loader';
+import {HighlightKeyword} from './modules/highlightKeywords';
+import {PopupFilter} from './modules/popupFilter';
+import {Dropdown} from './modules/dropdown';
+import {CheckList} from './modules/checkList';
+import {RowsCounter} from './modules/rowsCounter';
+import {StatusBar} from './modules/statusBar';
+import {Paging} from './modules/paging';
+import {ClearButton} from './modules/clearButton';
+import {Help} from './modules/help';
+import {AlternateRows} from './modules/alternateRows';
+
+var global = window,
+    isValidDate = DateHelper.isValid,
+    formatDate = DateHelper.format,
+    doc = global.document;
+
+export class TableFilter{
+
+    /**
+     * TableFilter object constructor
+     * requires `table` or `id` arguments, `row` and `configuration` optional
+     * @param {DOMElement} table Table DOM element
+     * @param {String} id Table id
+     * @param {Number} row index indicating the 1st row
+     * @param {Object} configuration object
+     */
+    constructor(...args) {
+        if(args.length === 0){ return; }
+
+        this.id = null;
+        this.version = '{VERSION}';
+        this.year = new Date().getFullYear();
+        this.tbl = null;
+        this.startRow = null;
+        this.refRow = null;
+        this.headersRow = null;
+        this.cfg = {};
+        this.nbFilterableRows = null;
+        this.nbRows = null;
+        this.nbCells = null;
+        this._hasGrid = false;
+
+        // TODO: use for-of with babel plug-in
+        args.forEach((arg)=> {
+            let argtype = typeof arg;
+            if(argtype === 'object' && arg && arg.nodeName === 'TABLE'){
+                this.tbl = arg;
+                this.id = arg.id || `tf_${new Date().getTime()}_`;
+            } else if(argtype === 'string'){
+                this.id = arg;
+                this.tbl = Dom.id(arg);
+            } else if(argtype === 'number'){
+                this.startRow = arg;
+            } else if(argtype === 'object'){
+                this.cfg = arg;
+            }
+        });
+
+        if(!this.tbl || this.tbl.nodeName != 'TABLE' || this.getRowsNb() === 0){
+            throw new Error(
+                'Could not instantiate TableFilter: HTML table not found.');
+        }
+
+        // configuration object
+        let f = this.cfg;
+
+        //Start row et cols nb
+        this.refRow = this.startRow === null ? 2 : (this.startRow+1);
+        try{ this.nbCells = this.getCellsNb(this.refRow); }
+        catch(e){ this.nbCells = this.getCellsNb(0); }
+
+        //default script base path
+        this.basePath = f.base_path || 'tablefilter/';
+
+        /*** filter types ***/
+        this.fltTypeInp = 'input';
+        this.fltTypeSlc = 'select';
+        this.fltTypeMulti = 'multiple';
+        this.fltTypeCheckList = 'checklist';
+        this.fltTypeNone = 'none';
+
+        /*** filters' grid properties ***/
+
+        //enables/disables filter grid
+        this.fltGrid = f.grid === false ? false : true;
+
+        //enables/disables grid layout (fixed headers)
+        this.gridLayout = Boolean(f.grid_layout);
+
+        this.filtersRowIndex = isNaN(f.filters_row_index) ?
+            0 : f.filters_row_index;
+        this.headersRow = isNaN(f.headers_row_index) ?
+            (this.filtersRowIndex === 0 ? 1 : 0) : f.headers_row_index;
+
+        if(this.gridLayout){
+            if(this.headersRow > 1){
+                this.filtersRowIndex = this.headersRow+1;
+            } else {
+                this.filtersRowIndex = 1;
+                this.headersRow = 0;
+            }
+        }
+
+        //defines tag of the cells containing filters (td/th)
+        this.fltCellTag = f.filters_cell_tag!=='th' ||
+            f.filters_cell_tag!=='td' ? 'td' : f.filters_cell_tag;
+
+        //stores filters ids
+        this.fltIds = [];
+        //stores filters DOM elements
+        this.fltElms = [];
+        //stores filters values
+        this.searchArgs = null;
+        //stores valid rows indexes (rows visible upon filtering)
+        this.validRowsIndex = null;
+        //stores filters row element
+        this.fltGridEl = null;
+        //is first load boolean
+        this.isFirstLoad = true;
+        //container div for paging elements, reset btn etc.
+        this.infDiv = null;
+        //div for rows counter
+        this.lDiv = null;
+        //div for reset button and results per page select
+        this.rDiv = null;
+        //div for paging elements
+        this.mDiv = null;
+
+        //defines css class for div containing paging elements, rows counter etc
+        this.infDivCssClass = f.inf_div_css_class || 'inf';
+        //defines css class for left div
+        this.lDivCssClass = f.left_div_css_class || 'ldiv';
+        //defines css class for right div
+        this.rDivCssClass =  f.right_div_css_class || 'rdiv';
+        //defines css class for mid div
+        this.mDivCssClass = f.middle_div_css_class || 'mdiv';
+        //table container div css class
+        this.contDivCssClass = f.content_div_css_class || 'cont';
+
+        /*** filters' grid appearance ***/
+        //stylesheet file
+        this.stylePath = f.style_path || this.basePath + 'style/';
+        this.stylesheet = f.stylesheet || this.stylePath+'tablefilter.css';
+        this.stylesheetId = this.id + '_style';
+        //defines css class for filters row
+        this.fltsRowCssClass = f.flts_row_css_class || 'fltrow';
+         //enables/disables icons (paging, reset button)
+        this.enableIcons = f.enable_icons===false ? false : true;
+        //enables/disbles rows alternating bg colors
+        this.alternateRows = Boolean(f.alternate_rows);
+        //defines widths of columns
+        this.hasColWidths = Types.isArray(f.col_widths);
+        this.colWidths = this.hasColWidths ? f.col_widths : null;
+        //defines css class for filters
+        this.fltCssClass = f.flt_css_class || 'flt';
+        //defines css class for multiple selects filters
+        this.fltMultiCssClass = f.flt_multi_css_class || 'flt_multi';
+        //defines css class for filters
+        this.fltSmallCssClass = f.flt_small_css_class || 'flt_s';
+        //defines css class for single-filter
+        this.singleFltCssClass = f.single_flt_css_class || 'single_flt';
+
+        /*** filters' grid behaviours ***/
+        //enables/disables enter key
+        this.enterKey = f.enter_key===false ? false : true;
+        //calls function before filtering starts
+        this.onBeforeFilter = Types.isFn(f.on_before_filter) ?
+            f.on_before_filter : null;
+        //calls function after filtering
+        this.onAfterFilter = Types.isFn(f.on_after_filter) ?
+            f.on_after_filter : null;
+        //enables/disables case sensitivity
+        this.caseSensitive = Boolean(f.case_sensitive);
+        //has exact match per column
+        this.hasExactMatchByCol = Types.isArray(f.columns_exact_match);
+        this.exactMatchByCol = this.hasExactMatchByCol ?
+            f.columns_exact_match : [];
+        //enables/disbles exact match for search
+        this.exactMatch = Boolean(f.exact_match);
+        //refreshes drop-down lists upon validation
+        this.linkedFilters = Boolean(f.linked_filters);
+        //wheter excluded options are disabled
+        this.disableExcludedOptions = Boolean(f.disable_excluded_options);
+        //stores active filter element
+        this.activeFlt = null;
+        //id of active filter
+        this.activeFilterId = null;
+        //enables always visible rows
+        this.hasVisibleRows = Boolean(f.rows_always_visible);
+        //array containing always visible rows
+        this.visibleRows = this.hasVisibleRows ? f.rows_always_visible : [];
+        //enables/disables external filters generation
+        this.isExternalFlt = Boolean(f.external_flt_grid);
+        //array containing ids of external elements containing filters
+        this.externalFltTgtIds = f.external_flt_grid_ids || null;
+        //stores filters elements if isExternalFlt is true
+        this.externalFltEls = [];
+        //delays any filtering process if loader true
+        this.execDelay = !isNaN(f.exec_delay) ? parseInt(f.exec_delay,10) : 100;
+        //calls function when filters grid loaded
+        this.onFiltersLoaded = Types.isFn(f.on_filters_loaded) ?
+            f.on_filters_loaded : null;
+        //enables/disables single filter search
+        this.singleSearchFlt = Boolean(f.single_filter);
+        //calls function after row is validated
+        this.onRowValidated = Types.isFn(f.on_row_validated) ?
+            f.on_row_validated : null;
+        //array defining columns for customCellData event
+        this.customCellDataCols = f.custom_cell_data_cols ?
+            f.custom_cell_data_cols : [];
+        //calls custom function for retrieving cell data
+        this.customCellData = Types.isFn(f.custom_cell_data) ?
+            f.custom_cell_data : null;
+        //input watermark text array
+        this.watermark = f.watermark || '';
+        this.isWatermarkArray = Types.isArray(this.watermark);
+        //id of toolbar container element
+        this.toolBarTgtId = f.toolbar_target_id || null;
+        //enables/disables help div
+        this.help = Types.isUndef(f.help_instructions) ?
+            undefined : Boolean(f.help_instructions);
+        //popup filters
+        this.popupFilters = Boolean(f.popup_filters);
+        //active columns color
+        this.markActiveColumns = Boolean(f.mark_active_columns);
+        //defines css class for active column header
+        this.activeColumnsCssClass = f.active_columns_css_class ||
+            'activeHeader';
+        //calls function before active column header is marked
+        this.onBeforeActiveColumn = Types.isFn(f.on_before_active_column) ?
+            f.on_before_active_column : null;
+        //calls function after active column header is marked
+        this.onAfterActiveColumn = Types.isFn(f.on_after_active_column) ?
+            f.on_after_active_column : null;
+
+        /*** select filter's customisation and behaviours ***/
+        //defines 1st option text
+        this.displayAllText = f.display_all_text || 'Clear';
+        //enables/disables empty option in combo-box filters
+        this.enableEmptyOption = Boolean(f.enable_empty_option);
+        //defines empty option text
+        this.emptyText = f.empty_text || '(Empty)';
+        //enables/disables non empty option in combo-box filters
+        this.enableNonEmptyOption = Boolean(f.enable_non_empty_option);
+        //defines empty option text
+        this.nonEmptyText = f.non_empty_text || '(Non empty)';
+        //enables/disables onChange event on combo-box
+        this.onSlcChange = f.on_change===false ? false : true;
+        //enables/disables select options sorting
+        this.sortSlc = f.sort_select===false ? false : true;
+        //enables/disables ascending numeric options sorting
+        this.isSortNumAsc = Boolean(f.sort_num_asc);
+        this.sortNumAsc = this.isSortNumAsc ? f.sort_num_asc : null;
+        //enables/disables descending numeric options sorting
+        this.isSortNumDesc = Boolean(f.sort_num_desc);
+        this.sortNumDesc = this.isSortNumDesc ? f.sort_num_desc : null;
+        //Select filters are populated on demand
+        this.loadFltOnDemand = Boolean(f.load_filters_on_demand);
+        this.hasCustomOptions = Types.isObj(f.custom_options);
+        this.customOptions = f.custom_options;
+
+        /*** Filter operators ***/
+        this.rgxOperator = f.regexp_operator || 'rgx:';
+        this.emOperator = f.empty_operator || '[empty]';
+        this.nmOperator = f.nonempty_operator || '[nonempty]';
+        this.orOperator = f.or_operator || '||';
+        this.anOperator = f.and_operator || '&&';
+        this.grOperator = f.greater_operator || '>';
+        this.lwOperator = f.lower_operator || '<';
+        this.leOperator = f.lower_equal_operator || '<=';
+        this.geOperator = f.greater_equal_operator || '>=';
+        this.dfOperator = f.different_operator || '!';
+        this.lkOperator = f.like_operator || '*';
+        this.eqOperator = f.equal_operator || '=';
+        this.stOperator = f.start_with_operator || '{';
+        this.enOperator = f.end_with_operator || '}';
+        this.curExp = f.cur_exp || '^[¥£€$]';
+        this.separator = f.separator || ',';
+
+        /*** rows counter ***/
+        //show/hides rows counter
+        this.rowsCounter = Boolean(f.rows_counter);
+
+        /*** status bar ***/
+        //show/hides status bar
+        this.statusBar = Boolean(f.status_bar);
+
+        /*** loader ***/
+        //enables/disables loader/spinner indicator
+        this.loader = Boolean(f.loader);
+
+        /*** validation - reset buttons/links ***/
+        //show/hides filter's validation button
+        this.displayBtn = Boolean(f.btn);
+        //defines validation button text
+        this.btnText = f.btn_text || (!this.enableIcons ? 'Go' : '');
+        //defines css class for validation button
+        this.btnCssClass = f.btn_css_class ||
+            (!this.enableIcons ? 'btnflt' : 'btnflt_icon');
+        //show/hides reset link
+        this.btnReset = Boolean(f.btn_reset);
+        //defines css class for reset button
+        this.btnResetCssClass = f.btn_reset_css_class || 'reset';
+        //callback function before filters are cleared
+        this.onBeforeReset = Types.isFn(f.on_before_reset) ?
+            f.on_before_reset : null;
+        //callback function after filters are cleared
+        this.onAfterReset = Types.isFn(f.on_after_reset) ?
+            f.on_after_reset : null;
+
+        /*** paging ***/
+        //enables/disables table paging
+        this.paging = Boolean(f.paging);
+        this.nbVisibleRows = 0; //nb visible rows
+        this.nbHiddenRows = 0; //nb hidden rows
+
+        /*** autofilter on typing ***/
+        //enables/disables auto filtering, table is filtered when user stops
+        //typing
+        this.autoFilter = Boolean(f.auto_filter);
+        //onkeyup delay timer (msecs)
+        this.autoFilterDelay = !isNaN(f.auto_filter_delay) ?
+            f.auto_filter_delay : 900;
+        //typing indicator
+        this.isUserTyping = null;
+        this.autoFilterTimer = null;
+
+        /*** keyword highlighting ***/
+        //enables/disables keyword highlighting
+        this.highlightKeywords = Boolean(f.highlight_keywords);
+
+        /*** data types ***/
+        //defines default date type (european DMY)
+        this.defaultDateType = f.default_date_type || 'DMY';
+        //defines default thousands separator
+        //US = ',' EU = '.'
+        this.thousandsSeparator = f.thousands_separator || ',';
+        //defines default decimal separator
+        //US & javascript = '.' EU = ','
+        this.decimalSeparator = f.decimal_separator || '.';
+        //enables number format per column
+        this.hasColNbFormat = Types.isArray(f.col_number_format);
+        //array containing columns nb formats
+        this.colNbFormat = this.hasColNbFormat ? f.col_number_format : null;
+        //enables date type per column
+        this.hasColDateType = Types.isArray(f.col_date_type);
+        //array containing columns date type
+        this.colDateType = this.hasColDateType ? f.col_date_type : null;
+
+        /*** status messages ***/
+        //filtering
+        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 grid values
+        this.msgResetValues = f.msg_reset_grid_values ||
+            'Re-setting filters values...';
+        //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)...';
+
+        /*** ids prefixes ***/
+        //css class name added to table
+        this.prfxTf = 'TF';
+        //filters (inputs - selects)
+        this.prfxFlt = 'flt';
+        //validation button
+        this.prfxValButton = 'btn';
+        //container div for paging elements, rows counter etc.
+        this.prfxInfDiv = 'inf_';
+        //left div
+        this.prfxLDiv = 'ldiv_';
+        //right div
+        this.prfxRDiv = 'rdiv_';
+        //middle div
+        this.prfxMDiv = 'mdiv_';
+        //filter values cookie
+        this.prfxCookieFltsValues = 'tf_flts_';
+        //page nb cookie
+        this.prfxCookiePageNb = 'tf_pgnb_';
+        //page length cookie
+        this.prfxCookiePageLen = 'tf_pglen_';
+
+        /*** cookies ***/
+        this.hasStoredValues = false;
+        //remembers filters values on page load
+        this.rememberGridValues = Boolean(f.remember_grid_values);
+        //cookie storing filter values
+        this.fltsValuesCookie = this.prfxCookieFltsValues + this.id;
+        //remembers page nb on page load
+        this.rememberPageNb = this.paging && f.remember_page_number;
+        //cookie storing page nb
+        this.pgNbCookie = this.prfxCookiePageNb + this.id;
+        //remembers page length on page load
+        this.rememberPageLen = this.paging && f.remember_page_length;
+        //cookie storing page length
+        this.pgLenCookie = this.prfxCookiePageLen + this.id;
+
+        /*** extensions ***/
+        //imports external script
+        this.extensions = f.extensions;
+        this.hasExtensions = Types.isArray(this.extensions);
+
+        /*** themes ***/
+        this.enableDefaultTheme = Boolean(f.enable_default_theme);
+        //imports themes
+        this.hasThemes = (this.enableDefaultTheme || Types.isArray(f.themes));
+        this.themes = f.themes || [];
+        //themes path
+        this.themesPath = f.themes_path || this.stylePath + 'themes/';
+
+        // Features registry
+        this.Mod = {};
+
+        // Extensions registry
+        this.ExtRegistry = {};
+
+        /*** TF events ***/
+        this.Evt = {
+            name: {
+                filter: 'Filter',
+                dropdown: 'DropDown',
+                checklist: 'CheckList',
+                changepage: 'ChangePage',
+                clear: 'Clear',
+                changeresultsperpage: 'ChangeResults',
+                resetvalues: 'ResetValues',
+                resetpage: 'ResetPage',
+                resetpagelength: 'ResetPageLength',
+                loadextensions: 'LoadExtensions',
+                loadthemes: 'LoadThemes'
+            },
+
+            // Detect <enter> key
+            detectKey(e) {
+                if(!this.enterKey){ return; }
+                let _ev = e || global.event;
+                if(_ev){
+                    let key = Event.keyCode(_ev);
+                    if(key===13){
+                        this.filter();
+                        Event.cancel(_ev);
+                        Event.stop(_ev);
+                    } else {
+                        this.isUserTyping = true;
+                        global.clearInterval(this.autoFilterTimer);
+                        this.autoFilterTimer = null;
+                    }
+                }
+            },
+            // if auto-filter on, detect user is typing and filter columns
+            onKeyUp(e) {
+                if(!this.autoFilter){
+                    return;
+                }
+                let _ev = e || global.event;
+                let key = Event.keyCode(_ev);
+                this.isUserTyping = false;
+
+                function filter() {
+                    /*jshint validthis:true */
+                    global.clearInterval(this.autoFilterTimer);
+                    this.autoFilterTimer = null;
+                    if(!this.isUserTyping){
+                        this.filter();
+                        this.isUserTyping = null;
+                    }
+                }
+
+                if(key!==13 && key!==9 && key!==27 && key!==38 && key!==40) {
+                    if(this.autoFilterTimer === null){
+                        this.autoFilterTimer = global.setInterval(
+                            filter.bind(this), this.autoFilterDelay);
+                    }
+                } else {
+                    global.clearInterval(this.autoFilterTimer);
+                    this.autoFilterTimer = null;
+                }
+            },
+            // if auto-filter on, detect user is typing
+            onKeyDown() {
+                if(!this.autoFilter) { return; }
+                this.isUserTyping = true;
+            },
+            // if auto-filter on, clear interval on filter blur
+            onInpBlur() {
+                if(this.autoFilter){
+                    this.isUserTyping = false;
+                    global.clearInterval(this.autoFilterTimer);
+                }
+                // TODO: hack to prevent ezEditTable enter key event hijaking.
+                // Needs to be fixed in the vendor's library
+                if(this.hasExtension('advancedGrid')){
+                    var advGrid = this.extension('advancedGrid');
+                    var ezEditTable = advGrid._ezEditTable;
+                    if(advGrid.cfg.editable){
+                        ezEditTable.Editable.Set();
+                    }
+                    if(advGrid.cfg.selection){
+                        ezEditTable.Selection.Set();
+                    }
+                }
+            },
+            // set focused text-box filter as active
+            onInpFocus(e) {
+                let _ev = e || global.event;
+                let elm = Event.target(_ev);
+                this.activeFilterId = elm.getAttribute('id');
+                this.activeFlt = Dom.id(this.activeFilterId);
+                if(this.popupFilters){
+                    Event.cancel(_ev);
+                    Event.stop(_ev);
+                }
+                // TODO: hack to prevent ezEditTable enter key event hijaking.
+                // Needs to be fixed in the vendor's library
+                if(this.hasExtension('advancedGrid')){
+                    var advGrid = this.extension('advancedGrid');
+                    var ezEditTable = advGrid._ezEditTable;
+                    if(advGrid.cfg.editable){
+                        ezEditTable.Editable.Remove();
+                    }
+                    if(advGrid.cfg.selection){
+                        ezEditTable.Selection.Remove();
+                    }
+                }
+            },
+            // set focused drop-down filter as active
+            onSlcFocus(e) {
+                let _ev = e || global.event;
+                let elm = Event.target(_ev);
+                this.activeFilterId = elm.getAttribute('id');
+                this.activeFlt = Dom.id(this.activeFilterId);
+                // select is populated when element has focus
+                if(this.loadFltOnDemand && elm.getAttribute('filled') === '0'){
+                    let ct = elm.getAttribute('ct');
+                    this.Mod.dropdown._build(ct);
+                }
+                if(this.popupFilters){
+                    Event.cancel(_ev);
+                    Event.stop(_ev);
+                }
+            },
+            // filter columns on drop-down filter change
+            onSlcChange(e) {
+                if(!this.activeFlt){ return; }
+                let _ev = e || global.event;
+                if(this.popupFilters){ Event.stop(_ev); }
+                if(this.onSlcChange){ this.filter(); }
+            },
+            // fill checklist filter on click if required
+            onCheckListClick(e) {
+                let _ev = e || global.event;
+                let elm = Event.target(_ev);
+                if(this.loadFltOnDemand && elm.getAttribute('filled') === '0'){
+                    let ct = elm.getAttribute('ct');
+                    this.Mod.checkList._build(ct);
+                    this.Mod.checkList.checkListDiv[ct].onclick = null;
+                    this.Mod.checkList.checkListDiv[ct].title = '';
+                }
+            },
+            // filter when validation button clicked
+            onBtnClick() {
+                this.filter();
+            }
+        };
+    }
+
+    /**
+     * Initialise filtering grid bar behaviours and layout
+     *
+     * TODO: decompose in smaller methods
+     */
+    init(){
+        if(this._hasGrid){
+            return;
+        }
+        if(!this.tbl){
+            this.tbl = Dom.id(this.id);
+        }
+        if(this.gridLayout){
+            this.refRow = this.startRow===null ? 0 : this.startRow;
+        }
+        if(this.popupFilters &&
+            ((this.filtersRowIndex === 0 && this.headersRow === 1) ||
+            this.gridLayout)){
+            this.headersRow = 0;
+        }
+
+        let Mod = this.Mod;
+        let n = this.singleSearchFlt ? 1 : this.nbCells,
+            inpclass;
+
+        //loads stylesheet if not imported
+        this.import(this.stylesheetId, this.stylesheet, null, 'link');
+
+        //loads theme
+        if(this.hasThemes){ this._loadThemes(); }
+
+        if(this.rememberGridValues || this.rememberPageNb ||
+            this.rememberPageLen){
+            Mod.store = new Store(this);
+        }
+
+        if(this.gridLayout){
+            Mod.gridLayout = new GridLayout(this);
+            Mod.gridLayout.init();
+        }
+
+        if(this.loader){
+            if(!Mod.loader){
+                Mod.loader = new Loader(this);
+                Mod.loader.init();
+            }
+        }
+
+        if(this.highlightKeywords){
+            Mod.highlightKeyword = new HighlightKeyword(this);
+        }
+
+        if(this.popupFilters){
+            if(!Mod.popupFilter){
+                Mod.popupFilter = new PopupFilter(this);
+            }
+            Mod.popupFilter.init();
+        }
+
+        //filters grid is not generated
+        if(!this.fltGrid){
+            this.refRow = this.refRow-1;
+            if(this.gridLayout){
+                this.refRow = 0;
+            }
+            this.nbFilterableRows = this.getRowsNb();
+            this.nbVisibleRows = this.nbFilterableRows;
+            this.nbRows = this.nbFilterableRows + this.refRow;
+        } else {
+            if(this.isFirstLoad){
+                let fltrow;
+                if(!this.gridLayout){
+                    let thead = Dom.tag(this.tbl, 'thead');
+                    if(thead.length > 0){
+                        fltrow = thead[0].insertRow(this.filtersRowIndex);
+                    } else {
+                        fltrow = this.tbl.insertRow(this.filtersRowIndex);
+                    }
+
+                    if(this.headersRow > 1 &&
+                        this.filtersRowIndex <= this.headersRow &&
+                        !this.popupFilters){
+                        this.headersRow++;
+                    }
+                    if(this.popupFilters){
+                        this.headersRow++;
+                    }
+
+                    fltrow.className = this.fltsRowCssClass;
+
+                    if(this.isExternalFlt || this.popupFilters){
+                        fltrow.style.display = 'none';
+                    }
+                }
+
+                this.nbFilterableRows = this.getRowsNb();
+                this.nbVisibleRows = this.nbFilterableRows;
+                this.nbRows = this.tbl.rows.length;
+
+                for(let i=0; i<n; i++){// this loop adds filters
+
+                    if(this.popupFilters){
+                        Mod.popupFilter.build(i);
+                    }
+
+                    let fltcell = Dom.create(this.fltCellTag),
+                        col = this.getFilterType(i),
+                        externalFltTgtId =
+                            this.isExternalFlt && this.externalFltTgtIds ?
+                            this.externalFltTgtIds[i] : null;
+
+                    if(this.singleSearchFlt){
+                        fltcell.colSpan = this.nbCells;
+                    }
+                    if(!this.gridLayout){
+                        fltrow.appendChild(fltcell);
+                    }
+                    inpclass = (i==n-1 && this.displayBtn) ?
+                        this.fltSmallCssClass : this.fltCssClass;
+
+                    //only 1 input for single search
+                    if(this.singleSearchFlt){
+                        col = this.fltTypeInp;
+                        inpclass = this.singleFltCssClass;
+                    }
+
+                    //drop-down filters
+                    if(col===this.fltTypeSlc || col===this.fltTypeMulti){
+                        if(!Mod.dropdown){
+                            Mod.dropdown = new Dropdown(this);
+                        }
+                        let dropdown = Mod.dropdown;
+
+                        let slc = Dom.create(this.fltTypeSlc,
+                                ['id', this.prfxFlt+i+'_'+this.id],
+                                ['ct', i], ['filled', '0']
+                            );
+
+                        if(col===this.fltTypeMulti){
+                            slc.multiple = this.fltTypeMulti;
+                            slc.title = dropdown.multipleSlcTooltip;
+                        }
+                        slc.className = Str.lower(col)===this.fltTypeSlc ?
+                            inpclass : this.fltMultiCssClass;// for ie<=6
+
+                        //filter is appended in desired external element
+                        if(externalFltTgtId){
+                            Dom.id(externalFltTgtId).appendChild(slc);
+                            this.externalFltEls.push(slc);
+                        } else {
+                            fltcell.appendChild(slc);
+                        }
+
+                        this.fltIds.push(this.prfxFlt+i+'_'+this.id);
+
+                        if(!this.loadFltOnDemand){
+                            dropdown._build(i);
+                        }
+
+                        Event.add(slc, 'keypress',
+                            this.Evt.detectKey.bind(this));
+                        Event.add(slc, 'change',
+                            this.Evt.onSlcChange.bind(this));
+                        Event.add(slc, 'focus', this.Evt.onSlcFocus.bind(this));
+
+                        //1st option is created here since dropdown.build isn't
+                        //invoked
+                        if(this.loadFltOnDemand){
+                            let opt0 = Dom.createOpt(this.displayAllText, '');
+                            slc.appendChild(opt0);
+                        }
+                    }
+                    // checklist
+                    else if(col===this.fltTypeCheckList){
+                        let checkList;
+                        Mod.checkList = new CheckList(this);
+                        checkList = Mod.checkList;
+
+                        let divCont = Dom.create('div',
+                            ['id', checkList.prfxCheckListDiv+i+'_'+this.id],
+                            ['ct', i], ['filled', '0']);
+                        divCont.className = checkList.checkListDivCssClass;
+
+                        //filter is appended in desired element
+                        if(externalFltTgtId){
+                            Dom.id(externalFltTgtId).appendChild(divCont);
+                            this.externalFltEls.push(divCont);
+                        } else {
+                            fltcell.appendChild(divCont);
+                        }
+
+                        checkList.checkListDiv[i] = divCont;
+                        this.fltIds.push(this.prfxFlt+i+'_'+this.id);
+                        if(!this.loadFltOnDemand){
+                            checkList._build(i);
+                        }
+
+                        if(this.loadFltOnDemand){
+                            Event.add(divCont, 'click',
+                                this.Evt.onCheckListClick.bind(this));
+                            divCont.appendChild(
+                                Dom.text(checkList.activateCheckListTxt));
+                        }
+                    }
+
+                    else{
+                        //show/hide input
+                        let inptype = col===this.fltTypeInp ? 'text' : 'hidden';
+                        let inp = Dom.create(this.fltTypeInp,
+                            ['id',this.prfxFlt+i+'_'+this.id],
+                            ['type',inptype], ['ct',i]);
+                        if(inptype!=='hidden' && this.watermark){
+                            inp.setAttribute(
+                                'placeholder',
+                                this.isWatermarkArray ?
+                                    (this.watermark[i] || '') : this.watermark
+                            );
+                        }
+                        inp.className = inpclass;
+                        Event.add(inp, 'focus', this.Evt.onInpFocus.bind(this));
+
+                        //filter is appended in desired element
+                        if(externalFltTgtId){
+                            Dom.id(externalFltTgtId).appendChild(inp);
+                            this.externalFltEls.push(inp);
+                        } else {
+                            fltcell.appendChild(inp);
+                        }
+
+                        this.fltIds.push(this.prfxFlt+i+'_'+this.id);
+
+                        Event.add(inp, 'keypress',
+                            this.Evt.detectKey.bind(this));
+                        Event.add(inp, 'keydown',
+                            this.Evt.onKeyDown.bind(this));
+                        Event.add(inp, 'keyup', this.Evt.onKeyUp.bind(this));
+                        Event.add(inp, 'blur', this.Evt.onInpBlur.bind(this));
+
+                        if(this.rememberGridValues){
+                            let flts_values = this.Mod.store.getFilterValues(
+                                this.fltsValuesCookie);
+                            if(flts_values[i]!=' '){
+                                this.setFilterValue(i, flts_values[i], false);
+                            }
+                        }
+                    }
+                    // this adds submit button
+                    if(i==n-1 && this.displayBtn){
+                        let btn = Dom.create(this.fltTypeInp,
+                            ['id',this.prfxValButton+i+'_'+this.id],
+                            ['type','button'], ['value',this.btnText]);
+                        btn.className = this.btnCssClass;
+
+                        //filter is appended in desired element
+                        if(externalFltTgtId){
+                            Dom.id(externalFltTgtId).appendChild(btn);
+                        } else{
+                            fltcell.appendChild(btn);
+                        }
+
+                        Event.add(btn, 'click', this.Evt.onBtnClick.bind(this));
+                    }//if
+
+                }// for i
+
+            } else {
+                this._resetGrid();
+            }//if isFirstLoad
+
+        }//if this.fltGrid
+
+        /* Filter behaviours */
+        if(this.hasVisibleRows){
+            this.enforceVisibility();
+        }
+        if(this.rowsCounter){
+            Mod.rowsCounter = new RowsCounter(this);
+            Mod.rowsCounter.init();
+        }
+        if(this.statusBar){
+            Mod.statusBar = new StatusBar(this);
+            Mod.statusBar.init();
+        }
+        if(this.paging || Mod.paging){
+            if(!Mod.paging){
+                Mod.paging = new Paging(this);
+                Mod.paging.init();
+            }
+            Mod.paging.reset();
+        }
+        if(this.btnReset){
+            Mod.clearButton = new ClearButton(this);
+            Mod.clearButton.init();
+        }
+        if(this.help){
+            if(!Mod.help){
+                Mod.help = new Help(this);
+            }
+            Mod.help.init();
+        }
+        if(this.hasColWidths && !this.gridLayout){
+            this.setColWidths();
+        }
+        if(this.alternateRows){
+            Mod.alternateRows = new AlternateRows(this);
+            Mod.alternateRows.init();
+        }
+
+        this.isFirstLoad = false;
+        this._hasGrid = true;
+
+        if(this.rememberGridValues || this.rememberPageLen ||
+            this.rememberPageNb){
+            this.resetValues();
+        }
+
+        //TF css class is added to table
+        if(!this.gridLayout){
+            Dom.addClass(this.tbl, this.prfxTf);
+        }
+
+        if(this.loader){
+            Mod.loader.show('none');
+        }
+
+        /* Loads extensions */
+        if(this.hasExtensions){
+            this.initExtensions();
+        }
+
+        if(this.onFiltersLoaded){
+            this.onFiltersLoaded.call(null, this);
+        }
+    }
+
+    /**
+     * Manages state messages
+     * @param {String} evt Event name
+     * @param {Object} cfg Config object
+     */
+    EvtManager(evt,
+        cfg={ slcIndex: null, slcExternal: false, slcId: null, pgIndex: null }){
+        let slcIndex = cfg.slcIndex;
+        let slcExternal = cfg.slcExternal;
+        let slcId = cfg.slcId;
+        let pgIndex = cfg.pgIndex;
+        let cpt = this.Mod;
+
+        function efx(){
+            /*jshint validthis:true */
+            let ev = this.Evt.name;
+
+            switch(evt){
+                case ev.filter:
+                    this._filter();
+                break;
+                case ev.dropdown:
+                    if(this.linkedFilters){
+                        cpt.dropdown._build(slcIndex, true);
+                    } else {
+                        cpt.dropdown._build(
+                            slcIndex, false, slcExternal, slcId);
+                    }
+                break;
+                case ev.checklist:
+                    cpt.checkList._build(slcIndex, slcExternal, slcId);
+                break;
+                case ev.changepage:
+                    cpt.paging._changePage(pgIndex);
+                break;
+                case ev.clear:
+                    this._clearFilters();
+                    this._filter();
+                break;
+                case ev.changeresultsperpage:
+                    cpt.paging._changeResultsPerPage();
+                break;
+                case ev.resetvalues:
+                    this._resetValues();
+                    this._filter();
+                break;
+                case ev.resetpage:
+                    cpt.paging._resetPage(this.pgNbCookie);
+                break;
+                case ev.resetpagelength:
+                    cpt.paging._resetPageLength(this.pgLenCookie);
+                break;
+                case ev.loadextensions:
+                    this._loadExtensions();
+                break;
+                case ev.loadthemes:
+                    this._loadThemes();
+                break;
+            }
+            if(this.statusBar){
+                cpt.statusBar.message('');
+            }
+            if(this.loader){
+                cpt.loader.show('none');
+            }
+        }
+
+        if(!this.loader && !this.statusBar && !this.linkedFilters) {
+            efx.call(this);
+        } else {
+            if(this.loader){
+                cpt.loader.show('');
+            }
+            if(this.statusBar){
+                cpt.statusBar.message(this['msg'+evt]);
+            }
+            global.setTimeout(efx.bind(this), this.execDelay);
+        }
+    }
+
+    /**
+     * Return a feature instance for a given name
+     * @param  {String} name Name of the feature
+     * @return {Object}
+     */
+    feature(name){
+        return this.Mod[name];
+    }
+
+    /**
+     * Initialise all the extensions defined in the configuration object
+     */
+    initExtensions(){
+        let exts = this.extensions;
+
+        for(let i=0, len=exts.length; i<len; i++){
+            let ext = exts[i];
+            if(!this.ExtRegistry[ext.name]){
+                this.loadExtension(ext);
+            }
+        }
+    }
+
+    /**
+     * Load an extension module
+     * @param  {Object} ext Extension config object
+     */
+    loadExtension(ext){
+        if(!ext || !ext.name){
+            return;
+        }
+
+        let name = ext.name;
+        let path = ext.path;
+        let modulePath;
+
+        if(name && path){
+            modulePath = ext.path + name;
+        } else {
+            name = name.replace('.js', '');
+            modulePath = 'extensions/{}/{}'.replace(/{}/g, name);
+        }
+
+        // Trick to set config's publicPath dynamically for Webpack...
+        __webpack_public_path__ = this.basePath;
+
+        require(['./' + modulePath], (mod)=> {
+            let inst = new mod(this, ext);
+            inst.init();
+            this.ExtRegistry[name] = inst;
+        });
+    }
+
+    /**
+     * Get an extension instance
+     * @param  {String} name Name of the extension
+     * @return {Object}      Extension instance
+     */
+    extension(name){
+        return this.ExtRegistry[name];
+    }
+
+    /**
+     * Check passed extension name exists
+     * @param  {String}  name Name of the extension
+     * @return {Boolean}
+     */
+    hasExtension(name){
+        return !Types.isEmpty(this.ExtRegistry[name]);
+    }
+
+    /**
+     * Destroy all the extensions defined in the configuration object
+     */
+    destroyExtensions(){
+        let exts = this.extensions;
+
+        for(let i=0, len=exts.length; i<len; i++){
+            let ext = exts[i];
+            let extInstance = this.ExtRegistry[ext.name];
+            if(extInstance){
+                extInstance.destroy();
+                this.ExtRegistry[ext.name] = null;
+            }
+        }
+    }
+
+    loadThemes(){
+        this.EvtManager(this.Evt.name.loadthemes);
+    }
+
+    /**
+     * Load themes defined in the configuration object
+     */
+    _loadThemes(){
+        let themes = this.themes;
+        //Default theme config
+        if(this.enableDefaultTheme){
+            let defaultTheme = { name: 'default' };
+            this.themes.push(defaultTheme);
+        }
+        if(Types.isArray(themes)){
+            for(let i=0, len=themes.length; i<len; i++){
+                let theme = themes[i];
+                let name = theme.name;
+                let path = theme.path;
+                let styleId = this.prfxTf + name;
+                if(name && !path){
+                    path = this.themesPath + name + '/' + name + '.css';
+                }
+                else if(!name && theme.path){
+                    name = 'theme{0}'.replace('{0}', i);
+                }
+
+                if(!this.isImported(path, 'link')){
+                    this.import(styleId, path, null, 'link');
+                }
+            }
+        }
+
+        //Some elements need to be overriden for default theme
+        //Reset button
+        this.btnResetText = null;
+        this.btnResetHtml = '<input type="button" value="" class="' +
+            this.btnResetCssClass+'" title="Clear filters" />';
+
+        //Paging buttons
+        this.btnPrevPageHtml = '<input type="button" value="" class="' +
+            this.btnPageCssClass+' previousPage" title="Previous page" />';
+        this.btnNextPageHtml = '<input type="button" value="" class="' +
+            this.btnPageCssClass+' nextPage" title="Next page" />';
+        this.btnFirstPageHtml = '<input type="button" value="" class="' +
+            this.btnPageCssClass+' firstPage" title="First page" />';
+        this.btnLastPageHtml = '<input type="button" value="" class="' +
+            this.btnPageCssClass+' lastPage" title="Last page" />';
+
+        //Loader
+        this.loader = true;
+        this.loaderHtml = '<div class="defaultLoader"></div>';
+        this.loaderText = null;
+    }
+
+    /**
+     * Return stylesheet DOM element for a given theme name
+     * @return {DOMElement} stylesheet element
+     */
+    getStylesheet(name='default'){
+        return Dom.id(this.prfxTf + name);
+    }
+
+    /**
+     * Destroy filter grid
+     */
+    destroy(){
+        if(!this._hasGrid){
+            return;
+        }
+        let rows = this.tbl.rows,
+            Mod = this.Mod;
+
+        this._clearFilters();
+
+        if(this.isExternalFlt && !this.popupFilters){
+            this.removeExternalFlts();
+        }
+        if(this.infDiv){
+            this.removeToolbar();
+        }
+        if(this.highlightKeywords){
+            Mod.highlightKeyword.unhighlightAll();
+        }
+        if(this.markActiveColumns){
+            this.clearActiveColumns();
+        }
+        if(this.hasExtensions){
+            this.destroyExtensions();
+        }
+
+        for(let j=this.refRow; j<this.nbRows; j++){
+            // validate row
+            this.validateRow(j, true);
+
+            //removes alternating colors
+            if(this.alternateRows){
+                Mod.alternateRows.removeRowBg(j);
+            }
+
+        }//for j
+
+        if(this.fltGrid && !this.gridLayout){
+            this.fltGridEl = rows[this.filtersRowIndex];
+            this.tbl.deleteRow(this.filtersRowIndex);
+        }
+
+        // Destroy modules
+        Object.keys(Mod).forEach(function(key) {
+            var feature = Mod[key];
+            if(feature && Types.isFn(feature.destroy)){
+                feature.destroy();
+            }
+        });
+
+        Dom.removeClass(this.tbl, this.prfxTf);
+        this.nbHiddenRows = 0;
+        this.validRowsIndex = null;
+        this.activeFlt = null;
+        this._hasGrid = false;
+        this.tbl = null;
+    }
+
+    /**
+     * Generate container element for paging, reset button, rows counter etc.
+     */
+    setToolbar(){
+        if(this.infDiv){
+            return;
+        }
+
+        /*** container div ***/
+        let infdiv = Dom.create('div', ['id', this.prfxInfDiv+this.id]);
+        infdiv.className = this.infDivCssClass;
+
+        //custom container
+        if(this.toolBarTgtId){
+            Dom.id(this.toolBarTgtId).appendChild(infdiv);
+        }
+        //grid-layout
+        else if(this.gridLayout){
+            let gridLayout = this.Mod.gridLayout;
+            gridLayout.tblMainCont.appendChild(infdiv);
+            infdiv.className = gridLayout.gridInfDivCssClass;
+        }
+        //default location: just above the table
+        else{
+            var cont = Dom.create('caption');
+            cont.appendChild(infdiv);
+            this.tbl.insertBefore(cont, this.tbl.firstChild);
+        }
+        this.infDiv = Dom.id(this.prfxInfDiv+this.id);
+
+        /*** left div containing rows # displayer ***/
+        let ldiv = Dom.create('div', ['id', this.prfxLDiv+this.id]);
+        ldiv.className = this.lDivCssClass;
+        infdiv.appendChild(ldiv);
+        this.lDiv = Dom.id(this.prfxLDiv+this.id);
+
+        /***    right div containing reset button
+                + nb results per page select    ***/
+        let rdiv = Dom.create('div', ['id', this.prfxRDiv+this.id]);
+        rdiv.className = this.rDivCssClass;
+        infdiv.appendChild(rdiv);
+        this.rDiv = Dom.id(this.prfxRDiv+this.id);
+
+        /*** mid div containing paging elements ***/
+        let mdiv = Dom.create('div', ['id', this.prfxMDiv+this.id]);
+        mdiv.className = this.mDivCssClass;
+        infdiv.appendChild(mdiv);
+        this.mDiv = Dom.id(this.prfxMDiv+this.id);
+
+        // Enable help instructions by default if topbar is generated and not
+        // explicitely set to false
+        if(Types.isUndef(this.help)){
+            if(!this.Mod.help){
+                this.Mod.help = new Help(this);
+            }
+            this.Mod.help.init();
+            this.help = true;
+        }
+    }
+
+    /**
+     * Remove toolbar container element
+     */
+    removeToolbar(){
+        if(!this.infDiv){
+            return;
+        }
+        this.infDiv.parentNode.removeChild(this.infDiv);
+        this.infDiv = null;
+
+        let tbl = this.tbl;
+        let captions = Dom.tag(tbl, 'caption');
+        if(captions.length > 0){
+            [].forEach.call(captions, function(elm) {
+                tbl.removeChild(elm);
+            });
+        }
+    }
+
+    /**
+     * Remove all the external column filters
+     */
+    removeExternalFlts(){
+        if(!this.isExternalFlt || !this.externalFltTgtIds){
+            return;
+        }
+        let ids = this.externalFltTgtIds,
+            len = ids.length;
+        for(let ct=0; ct<len; ct++){
+            let externalFltTgtId = ids[ct],
+                externalFlt = Dom.id(externalFltTgtId);
+            if(externalFlt){
+                externalFlt.innerHTML = '';
+            }
+        }
+    }
+
+    /**
+     * Check if given column implements a filter with custom options
+     * @param  {Number}  colIndex Column's index
+     * @return {Boolean}
+     */
+    isCustomOptions(colIndex) {
+        return this.hasCustomOptions &&
+            this.customOptions.cols.indexOf(colIndex) != -1;
+    }
+
+    /**
+     * Returns an array [[value0, value1 ...],[text0, text1 ...]] with the
+     * custom options values and texts
+     * @param  {Number} colIndex Column's index
+     * @return {Array}
+     */
+    getCustomOptions(colIndex){
+        if(Types.isEmpty(colIndex) || !this.isCustomOptions(colIndex)){
+            return;
+        }
+
+        let customOptions = this.customOptions;
+        let cols = customOptions.cols;
+        let optTxt = [], optArray = [];
+        let index = cols.indexOf(colIndex);
+        let slcValues = customOptions.values[index];
+        let slcTexts = customOptions.texts[index];
+        let slcSort = customOptions.sorts[index];
+
+        for(let r=0, len=slcValues.length; r<len; r++){
+            optArray.push(slcValues[r]);
+            if(slcTexts[r]){
+                optTxt.push(slcTexts[r]);
+            } else {
+                optTxt.push(slcValues[r]);
+            }
+        }
+        if(slcSort){
+            optArray.sort();
+            optTxt.sort();
+        }
+        return [optArray, optTxt];
+    }
+
+    resetValues(){
+        this.EvtManager(this.Evt.name.resetvalues);
+    }
+
+    /**
+     * Reset persisted filter values
+     */
+    _resetValues(){
+        //only loadFltOnDemand
+        if(this.rememberGridValues && this.loadFltOnDemand){
+            this._resetGridValues(this.fltsValuesCookie);
+        }
+        if(this.rememberPageLen && this.Mod.paging){
+            this.Mod.paging.resetPageLength(this.pgLenCookie);
+        }
+        if(this.rememberPageNb && this.Mod.paging){
+            this.Mod.paging.resetPage(this.pgNbCookie);
+        }
+    }
+
+    /**
+     * Reset persisted filter values when load filters on demand feature is
+     * enabled
+     * @param  {String} name cookie name storing filter values
+     */
+    _resetGridValues(name){
+        if(!this.loadFltOnDemand){
+            return;
+        }
+        let fltsValues = this.Mod.store.getFilterValues(name),
+            slcFltsIndex = this.getFiltersByType(this.fltTypeSlc, true),
+            multiFltsIndex = this.getFiltersByType(this.fltTypeMulti, true);
+
+        //if the number of columns is the same as before page reload
+        if(Number(fltsValues[(fltsValues.length-1)]) === this.fltIds.length){
+            for(let i=0; i<(fltsValues.length - 1); i++){
+                if(fltsValues[i]===' '){
+                    continue;
+                }
+                let s, opt;
+                let fltType = this.getFilterType(i);
+                // if loadFltOnDemand, drop-down needs to contain stored
+                // value(s) for filtering
+                if(fltType===this.fltTypeSlc || fltType===this.fltTypeMulti){
+                    let slc = Dom.id( this.fltIds[i] );
+                    slc.options[0].selected = false;
+
+                    //selects
+                    if(slcFltsIndex.indexOf(i) != -1){
+                        opt = Dom.createOpt(fltsValues[i],fltsValues[i],true);
+                        slc.appendChild(opt);
+                        this.hasStoredValues = true;
+                    }
+                    //multiple select
+                    if(multiFltsIndex.indexOf(i) != -1){
+                        s = fltsValues[i].split(' '+this.orOperator+' ');
+                        for(let j=0, len=s.length; j<len; j++){
+                            if(s[j]===''){
+                                continue;
+                            }
+                            opt = Dom.createOpt(s[j],s[j],true);
+                            slc.appendChild(opt);
+                            this.hasStoredValues = true;
+                        }
+                    }// if multiFltsIndex
+                }
+                else if(fltType===this.fltTypeCheckList){
+                    let checkList = this.Mod.checkList;
+                    let divChk = checkList.checkListDiv[i];
+                    divChk.title = divChk.innerHTML;
+                    divChk.innerHTML = '';
+
+                    let ul = Dom.create(
+                        'ul',['id',this.fltIds[i]],['colIndex',i]);
+                    ul.className = checkList.checkListCssClass;
+
+                    let li0 = Dom.createCheckItem(
+                        this.fltIds[i]+'_0', '', this.displayAllText);
+                    li0.className = checkList.checkListItemCssClass;
+                    ul.appendChild(li0);
+
+                    divChk.appendChild(ul);
+
+                    s = fltsValues[i].split(' '+this.orOperator+' ');
+                    for(let j=0, len=s.length; j<len; j++){
+                        if(s[j]===''){
+                            continue;
+                        }
+                        let li = Dom.createCheckItem(
+                            this.fltIds[i]+'_'+(j+1), s[j], s[j]);
+                        li.className = checkList.checkListItemCssClass;
+                        ul.appendChild(li);
+                        li.check.checked = true;
+                        checkList.setCheckListValues(li.check);
+                        this.hasStoredValues = true;
+                    }
+                }
+            }//end for
+
+            if(!this.hasStoredValues && this.paging){
+                this.Mod.paging.setPagingInfo();
+            }
+        }//end if
+    }
+
+    filter(){
+        this.EvtManager(this.Evt.name.filter);
+    }
+
+    /**
+     * Filter the table by retrieving the data from each cell in every single
+     * row and comparing it to the search term for current column. A row is
+     * hidden when all the search terms are not found in inspected row.
+     *
+     * TODO: Reduce complexity of this massive method
+     */
+    _filter(){
+        if(!this.fltGrid || (!this._hasGrid && !this.isFirstLoad)){
+            return;
+        }
+        //invoke onbefore callback
+        if(this.onBeforeFilter){
+            this.onBeforeFilter.call(null, this);
+        }
+
+        let row = this.tbl.rows,
+            Mod = this.Mod,
+            hiddenrows = 0;
+
+        this.validRowsIndex = [];
+
+        // removes keyword highlighting
+        if(this.highlightKeywords){
+            Mod.highlightKeyword.unhighlightAll();
+        }
+        //removes popup filters active icons
+        if(this.popupFilters){
+            Mod.popupFilter.buildIcons();
+        }
+        //removes active column header class
+        if(this.markActiveColumns){
+            this.clearActiveColumns();
+        }
+        // search args re-init
+        this.searchArgs = this.getFiltersValue();
+
+        var num_cell_data, nbFormat;
+        var re_le = new RegExp(this.leOperator),
+            re_ge = new RegExp(this.geOperator),
+            re_l = new RegExp(this.lwOperator),
+            re_g = new RegExp(this.grOperator),
+            re_d = new RegExp(this.dfOperator),
+            re_lk = new RegExp(Str.rgxEsc(this.lkOperator)),
+            re_eq = new RegExp(this.eqOperator),
+            re_st = new RegExp(this.stOperator),
+            re_en = new RegExp(this.enOperator),
+            // re_an = new RegExp(this.anOperator),
+            // re_cr = new RegExp(this.curExp),
+            re_em = this.emOperator,
+            re_nm = this.nmOperator,
+            re_re = new RegExp(Str.rgxEsc(this.rgxOperator));
+
+        //keyword highlighting
+        function highlight(str, ok, cell){
+            /*jshint validthis:true */
+            if(this.highlightKeywords && ok){
+                str = str.replace(re_lk, '');
+                str = str.replace(re_eq, '');
+                str = str.replace(re_st, '');
+                str = str.replace(re_en, '');
+                let w = str;
+                if(re_le.test(str) || re_ge.test(str) || re_l.test(str) ||
+                    re_g.test(str) || re_d.test(str)){
+                    w = Dom.getText(cell);
+                }
+                if(w !== ''){
+                    Mod.highlightKeyword.highlight(
+                        cell, w, Mod.highlightKeyword.highlightCssClass);
+                }
+            }
+        }
+
+        //looks for search argument in current row
+        function hasArg(sA, cell_data, j){
+            /*jshint validthis:true */
+            let occurence,
+                removeNbFormat = Helpers.removeNbFormat;
+            //Search arg operator tests
+            let hasLO = re_l.test(sA),
+                hasLE = re_le.test(sA),
+                hasGR = re_g.test(sA),
+                hasGE = re_ge.test(sA),
+                hasDF = re_d.test(sA),
+                hasEQ = re_eq.test(sA),
+                hasLK = re_lk.test(sA),
+                // hasAN = re_an.test(sA),
+                hasST = re_st.test(sA),
+                hasEN = re_en.test(sA),
+                hasEM = (re_em === sA),
+                hasNM = (re_nm === sA),
+                hasRE = re_re.test(sA);
+
+            //Search arg dates tests
+            let isLDate = hasLO && isValidDate(sA.replace(re_l,''), dtType);
+            let isLEDate = hasLE && isValidDate(sA.replace(re_le,''), dtType);
+            let isGDate = hasGR && isValidDate(sA.replace(re_g,''), dtType);
+            let isGEDate = hasGE && isValidDate(sA.replace(re_ge,''), dtType);
+            let isDFDate = hasDF && isValidDate(sA.replace(re_d,''), dtType);
+            let isEQDate = hasEQ && isValidDate(sA.replace(re_eq,''), dtType);
+
+            let dte1, dte2;
+            //dates
+            if(isValidDate(cell_data,dtType)){
+                dte1 = formatDate(cell_data,dtType);
+                // lower date
+                if(isLDate){
+                    dte2 = formatDate(sA.replace(re_l,''), dtType);
+                    occurence = dte1 < dte2;
+                }
+                // lower equal date
+                else if(isLEDate){
+                    dte2 = formatDate(sA.replace(re_le,''), dtType);
+                    occurence = dte1 <= dte2;
+                }
+                // greater equal date
+                else if(isGEDate){
+                    dte2 = formatDate(sA.replace(re_ge,''), dtType);
+                    occurence = dte1 >= dte2;
+                }
+                // greater date
+                else if(isGDate){
+                    dte2 = formatDate(sA.replace(re_g,''), dtType);
+                    occurence = dte1 > dte2;
+                }
+                // different date
+                else if(isDFDate){
+                    dte2 = formatDate(sA.replace(re_d,''), dtType);
+                    occurence = dte1.toString() != dte2.toString();
+                }
+                // equal date
+                else if(isEQDate){
+                    dte2 = formatDate(sA.replace(re_eq,''), dtType);
+                    occurence = dte1.toString() == dte2.toString();
+                }
+                // searched keyword with * operator doesn't have to be a date
+                else if(re_lk.test(sA)){// like date
+                    occurence = this._containsStr(
+                        sA.replace(re_lk,''), cell_data, false);
+                }
+                else if(isValidDate(sA,dtType)){
+                    dte2 = formatDate(sA,dtType);
+                    occurence = dte1.toString() == dte2.toString();
+                }
+                //empty
+                else if(hasEM){
+                    occurence = Str.isEmpty(cell_data);
+                }
+                //non-empty
+                else if(hasNM){
+                    occurence = !Str.isEmpty(cell_data);
+                }
+            }
+
+            else{
+                //first numbers need to be formated
+                if(this.hasColNbFormat && this.colNbFormat[j]){
+                    num_cell_data = removeNbFormat(
+                        cell_data, this.colNbFormat[j]);
+                    nbFormat = this.colNbFormat[j];
+                } else {
+                    if(this.thousandsSeparator === ',' &&
+                        this.decimalSeparator === '.'){
+                        num_cell_data = removeNbFormat(cell_data, 'us');
+                        nbFormat = 'us';
+                    } else {
+                        num_cell_data = removeNbFormat(cell_data, 'eu');
+                        nbFormat = 'eu';
+                    }
+                }
+
+                // first checks if there is any operator (<,>,<=,>=,!,*,=,{,},
+                // rgx:)
+                // lower equal
+                if(hasLE){
+                    occurence = num_cell_data <= removeNbFormat(
+                        sA.replace(re_le, ''), nbFormat);
+                }
+                //greater equal
+                else if(hasGE){
+                    occurence = num_cell_data >= removeNbFormat(
+                        sA.replace(re_ge, ''), nbFormat);
+                }
+                //lower
+                else if(hasLO){
+                    occurence = num_cell_data < removeNbFormat(
+                        sA.replace(re_l, ''), nbFormat);
+                }
+                //greater
+                else if(hasGR){
+                    occurence = num_cell_data > removeNbFormat(
+                        sA.replace(re_g, ''), nbFormat);
+                }
+                //different
+                else if(hasDF){
+                    occurence = this._containsStr(
+                        sA.replace(re_d, ''), cell_data) ? false : true;
+                }
+                //like
+                else if(hasLK){
+                    occurence = this._containsStr(
+                        sA.replace(re_lk, ''), cell_data, false);
+                }
+                //equal
+                else if(hasEQ){
+                    occurence = this._containsStr(
+                        sA.replace(re_eq, ''), cell_data, true);
+                }
+                //starts with
+                else if(hasST){
+                    occurence = cell_data.indexOf(sA.replace(re_st, ''))===0 ?
+                        true : false;
+                }
+                //ends with
+                else if(hasEN){
+                    let searchArg = sA.replace(re_en, '');
+                    occurence =
+                        cell_data.lastIndexOf(searchArg,cell_data.length-1) ===
+                        (cell_data.length-1)-(searchArg.length-1) &&
+                        cell_data.lastIndexOf(
+                            searchArg, cell_data.length-1) > -1 ? true : false;
+                }
+                //empty
+                else if(hasEM){
+                    occurence = Str.isEmpty(cell_data);
+                }
+                //non-empty
+                else if(hasNM){
+                    occurence = !Str.isEmpty(cell_data);
+                }
+                //regexp
+                else if(hasRE){
+                    //in case regexp fires an exception
+                    try{
+                        //operator is removed
+                        let srchArg = sA.replace(re_re,'');
+                        let rgx = new RegExp(srchArg);
+                        occurence = rgx.test(cell_data);
+                    } catch(e) { occurence = false; }
+                } else {
+                    occurence = this._containsStr(sA, cell_data,
+                        this.isExactMatch(j));
+                }
+
+            }//else
+            return occurence;
+        }//fn
+
+        for(let k=this.refRow; k<this.nbRows; k++){
+            /*** if table already filtered some rows are not visible ***/
+            if(row[k].style.display === 'none'){
+                row[k].style.display = '';
+            }
+
+            let cell = row[k].cells,
+                nchilds = cell.length;
+
+            // checks if row has exact cell #
+            if(nchilds !== this.nbCells){
+                continue;
+            }
+
+            let occurence = [],
+                isRowValid = true,
+                //only for single filter search
+                singleFltRowValid = false;
+
+            // this loop retrieves cell data
+            for(let j=0; j<nchilds; j++){
+                //searched keyword
+                let sA = this.searchArgs[this.singleSearchFlt ? 0 : j];
+                var dtType = this.hasColDateType ?
+                        this.colDateType[j] : this.defaultDateType;
+                if(sA === ''){
+                    continue;
+                }
+
+                let cell_data = Str.matchCase(this.getCellData(cell[j]),
+                    this.caseSensitive);
+
+                //multiple search parameter operator ||
+                let sAOrSplit = sA.split(this.orOperator),
+                //multiple search || parameter boolean
+                hasMultiOrSA = (sAOrSplit.length>1) ? true : false,
+                //multiple search parameter operator &&
+                sAAndSplit = sA.split(this.anOperator),
+                //multiple search && parameter boolean
+                hasMultiAndSA = sAAndSplit.length>1 ? true : false;
+
+                //multiple sarch parameters
+                if(hasMultiOrSA || hasMultiAndSA){
+                    let cS,
+                        occur = false,
+                        s = hasMultiOrSA ? sAOrSplit : sAAndSplit;
+                    for(let w=0, len=s.length; w<len; w++){
+                        cS = Str.trim(s[w]);
+                        occur = hasArg.call(this, cS, cell_data, j);
+                        highlight.call(this, cS, occur, cell[j]);
+                        if(hasMultiOrSA && occur){
+                            break;
+                        }
+                        if(hasMultiAndSA && !occur){
+                            break;
+                        }
+                    }
+                    occurence[j] = occur;
+                }
+                //single search parameter
+                else {
+                    occurence[j] =
+                        hasArg.call(this, Str.trim(sA), cell_data, j);
+                    highlight.call(this, sA, occurence[j], cell[j]);
+                }//else single param
+
+                if(!occurence[j]){
+                    isRowValid = false;
+                }
+                if(this.singleSearchFlt && occurence[j]){
+                    singleFltRowValid = true;
+                }
+                if(this.popupFilters){
+                    Mod.popupFilter.buildIcon(j, true);
+                }
+                if(this.markActiveColumns){
+                    if(k === this.refRow){
+                        if(this.onBeforeActiveColumn){
+                            this.onBeforeActiveColumn.call(null, this, j);
+                        }
+                        Dom.addClass(
+                            this.getHeaderElement(j),
+                            this.activeColumnsCssClass);
+                        if(this.onAfterActiveColumn){
+                            this.onAfterActiveColumn.call(null, this, j);
+                        }
+                    }
+                }
+            }//for j
+
+            if(this.singleSearchFlt && singleFltRowValid){
+                isRowValid = true;
+            }
+
+            if(!isRowValid){
+                this.validateRow(k, false);
+                if(Mod.alternateRows){
+                    Mod.alternateRows.removeRowBg(k);
+                }
+                // always visible rows need to be counted as valid
+                if(this.hasVisibleRows && this.visibleRows.indexOf(k) !== -1){
+                    this.validRowsIndex.push(k);
+                } else {
+                    hiddenrows++;
+                }
+            } else {
+                this.validateRow(k, true);
+                this.validRowsIndex.push(k);
+                if(this.alternateRows){
+                    Mod.alternateRows.setRowBg(k, this.validRowsIndex.length);
+                }
+                if(this.onRowValidated){
+                    this.onRowValidated.call(null, this, k);
+                }
+            }
+        }// for k
+
+        this.nbVisibleRows = this.validRowsIndex.length;
+        this.nbHiddenRows = hiddenrows;
+
+        if(this.rememberGridValues){
+            Mod.store.saveFilterValues(this.fltsValuesCookie);
+        }
+        //applies filter props after filtering process
+        if(!this.paging){
+            this.applyProps();
+        } else {
+            // Shouldn't need to care of that here...
+            // TODO: provide a method in paging module
+            Mod.paging.startPagingRow = 0;
+            Mod.paging.currentPageNb = 1;
+            //
+            Mod.paging.setPagingInfo(this.validRowsIndex);
+        }
+        //invokes onafter callback
+        if(this.onAfterFilter){
+            this.onAfterFilter.call(null,this);
+        }
+    }
+
+    /**
+     * Re-apply the features/behaviour concerned by filtering/paging operation
+     *
+     * NOTE: this will disappear whenever custom events in place
+     */
+    applyProps(){
+        let Mod = this.Mod;
+
+        //shows rows always visible
+        if(this.hasVisibleRows){
+            this.enforceVisibility();
+        }
+        //columns operations
+        if(this.hasExtension('colOps')){
+            this.extension('colOps').calc();
+        }
+
+        //re-populates drop-down filters
+        if(this.linkedFilters){
+            this.linkFilters();
+        }
+
+        if(this.rowsCounter){
+            Mod.rowsCounter.refresh(this.nbVisibleRows);
+        }
+
+        if(this.popupFilters){
+            Mod.popupFilter.closeAll();
+        }
+    }
+
+    /**
+     * Return the data of a specified colum
+     * @param  {Number} colIndex Column index
+     * @param  {Boolean} includeHeaders  Optional: include headers row
+     * @param  {Boolean} num     Optional: return unformatted number
+     * @param  {Array} exclude   Optional: list of row indexes to be excluded
+     * @return {Array}           Flat list of data for a column
+     */
+    getColValues(colIndex, includeHeaders=false, num=false, exclude=[]){
+        if(!this.fltGrid){
+            return;
+        }
+        let row = this.tbl.rows,
+            colValues = [];
+
+        if(includeHeaders){
+            colValues.push(this.getHeadersText()[colIndex]);
+        }
+
+        for(let i=this.refRow; i<this.nbRows; i++){
+            let isExludedRow = false;
+            // checks if current row index appears in exclude array
+            if(exclude.length > 0){
+                isExludedRow = exclude.indexOf(i) != -1;
+            }
+            let cell = row[i].cells,
+                nchilds = cell.length;
+
+            // checks if row has exact cell # and is not excluded
+            if(nchilds === this.nbCells && !isExludedRow){
+                // this loop retrieves cell data
+                for(let j=0; j<nchilds; j++){
+                    if(j != colIndex || row[i].style.display !== ''){
+                        continue;
+                    }
+                    let cell_data = this.getCellData(cell[j]),
+                        nbFormat = this.colNbFormat ?
+                            this.colNbFormat[colIndex] : null,
+                        data = num ?
+                                Helpers.removeNbFormat(cell_data, nbFormat) :
+                                cell_data;
+                    colValues.push(data);
+                }
+            }
+        }
+        return colValues;
+    }
+
+    /**
+     * Return the filter's value of a specified column
+     * @param  {Number} index Column index
+     * @return {String}       Filter value
+     */
+    getFilterValue(index){
+        if(!this.fltGrid){
+            return;
+        }
+        let fltValue,
+            flt = this.getFilterElement(index);
+        if(!flt){
+            return '';
+        }
+
+        let fltColType = this.getFilterType(index);
+        if(fltColType !== this.fltTypeMulti &&
+            fltColType !== this.fltTypeCheckList){
+            fltValue = flt.value;
+        }
+        //mutiple select
+        else if(fltColType === this.fltTypeMulti){
+            fltValue = '';
+            for(let j=0, len=flt.options.length; j<len; j++){
+                if(flt.options[j].selected){
+                    fltValue = fltValue.concat(
+                        flt.options[j].value+' ' +
+                        this.orOperator + ' '
+                    );
+                }
+            }
+            //removes last operator ||
+            fltValue = fltValue.substr(0, fltValue.length-4);
+        }
+        //checklist
+        else if(fltColType === this.fltTypeCheckList){
+            if(flt.getAttribute('value') !== null){
+                fltValue = flt.getAttribute('value');
+                //removes last operator ||
+                fltValue = fltValue.substr(0, fltValue.length-3);
+            } else{
+                fltValue = '';
+            }
+        }
+        return fltValue;
+    }
+
+    /**
+     * Return the filters' values
+     * @return {Array} List of filters' values
+     */
+    getFiltersValue(){
+        if(!this.fltGrid){
+            return;
+        }
+        let searchArgs = [];
+        for(let i=0, len=this.fltIds.length; i<len; i++){
+            searchArgs.push(
+                Str.trim(
+                    Str.matchCase(this.getFilterValue(i), this.caseSensitive))
+            );
+        }
+        return searchArgs;
+    }
+
+    /**
+     * Return the ID of the filter of a specified column
+     * @param  {Number} index Column's index
+     * @return {String}       ID of the filter element
+     */
+    getFilterId(index){
+        if(!this.fltGrid){
+            return;
+        }
+        return this.fltIds[index];
+    }
+
+    /**
+     * Return the list of ids of filters matching a specified type.
+     * Note: hidden filters are also returned
+     *
+     * @param  {String} type  Filter type string ('input', 'select', 'multiple',
+     *                        'checklist')
+     * @param  {Boolean} bool If true returns columns indexes instead of IDs
+     * @return {[type]}       List of element IDs or column indexes
+     */
+    getFiltersByType(type, bool){
+        if(!this.fltGrid){
+            return;
+        }
+        let arr = [];
+        for(let i=0, len=this.fltIds.length; i<len; i++){
+            let fltType = this.getFilterType(i);
+            if(fltType === Str.lower(type)){
+                let a = bool ? i : this.fltIds[i];
+                arr.push(a);
+            }
+        }
+        return arr;
+    }
+
+    /**
+     * Return the filter's DOM element for a given column
+     * @param  {Number} index     Column's index
+     * @return {DOMElement}
+     */
+    getFilterElement(index){
+        let fltId = this.fltIds[index];
+        return Dom.id(fltId);
+    }
+
+    /**
+     * Return the number of cells for a given row index
+     * @param  {Number} rowIndex Index of the row
+     * @return {Number}          Number of cells
+     */
+    getCellsNb(rowIndex=0){
+        let tr = this.tbl.rows[rowIndex];
+        return tr.cells.length;
+    }
+
+    /**
+     * Return the number of filterable rows starting from reference row if
+     * defined
+     * @param  {Boolean} includeHeaders Include the headers row
+     * @return {Number}                 Number of filterable rows
+     */
+    getRowsNb(includeHeaders){
+        let s = Types.isUndef(this.refRow) ? 0 : this.refRow,
+            ntrs = this.tbl.rows.length;
+        if(includeHeaders){ s = 0; }
+        return parseInt(ntrs-s, 10);
+    }
+
+    /**
+     * Return the data of a given cell
+     * @param  {DOMElement} cell Cell's DOM object
+     * @return {String}
+     */
+    getCellData(cell){
+        var idx = cell.cellIndex;
+        //Check for customCellData callback
+        if(this.customCellData && this.customCellDataCols.indexOf(idx) != -1){
+            return this.customCellData.call(null, this, cell, idx);
+        } else {
+            return Dom.getText(cell);
+        }
+    }
+
+    /**
+     * Return the table data with following format:
+     * [
+     *     [rowIndex, [value0, value1...]],
+     *     [rowIndex, [value0, value1...]]
+     * ]
+     * @param  {Boolean} includeHeaders  Optional: include headers row
+     * @return {Array}
+     *
+     * TODO: provide an API returning data in JSON format
+     */
+    getTableData(includeHeaders=false){
+        let rows = this.tbl.rows;
+        let tblData = [];
+        if(includeHeaders){
+            tblData.push([this.getHeadersRowIndex(), this.getHeadersText()]);
+        }
+        for(let k=this.refRow; k<this.nbRows; k++){
+            let rowData = [k, []];
+            let cells = rows[k].cells;
+            for(let j=0, len=cells.length; j<len; j++){
+                let cellData = this.getCellData(cells[j]);
+                rowData[1].push(cellData);
+            }
+            tblData.push(rowData);
+        }
+        return tblData;
+    }
+
+    /**
+     * Return the filtered data with following format:
+     * [
+     *     [rowIndex, [value0, value1...]],
+     *     [rowIndex, [value0, value1...]]
+     * ]
+     * @param  {Boolean} includeHeaders  Optional: include headers row
+     * @return {Array}
+     *
+     * TODO: provide an API returning data in JSON format
+     */
+    getFilteredData(includeHeaders=false){
+        if(!this.validRowsIndex){
+            return [];
+        }
+        let rows = this.tbl.rows,
+            filteredData = [];
+        if(includeHeaders){
+            filteredData.push([this.getHeadersRowIndex(),
+                this.getHeadersText()]);
+        }
+
+        let validRows = this.getValidRows(true);
+        for(let i=0; i<validRows.length; i++){
+            let rData = [this.validRowsIndex[i], []],
+                cells = rows[this.validRowsIndex[i]].cells;
+            for(let k=0; k<cells.length; k++){
+                let cellData = this.getCellData(cells[k]);
+                rData[1].push(cellData);
+            }
+            filteredData.push(rData);
+        }
+        return filteredData;
+    }
+
+    /**
+     * Return the filtered data for a given column index
+     * @param  {Number} colIndex Colmun's index
+     * @param  {Boolean} includeHeaders  Optional: include headers row
+     * @return {Array}           Flat list of values ['val0','val1','val2'...]
+     *
+     * TODO: provide an API returning data in JSON format
+     */
+    getFilteredDataCol(colIndex, includeHeaders=false){
+        if(Types.isUndef(colIndex)){
+            return [];
+        }
+        let data =  this.getFilteredData(),
+            colData = [];
+        if(includeHeaders){
+            colData.push(this.getHeadersText()[colIndex]);
+        }
+        for(let i=0, len=data.length; i<len; i++){
+            let r = data[i],
+                //cols values of current row
+                d = r[1],
+                //data of searched column
+                c = d[colIndex];
+            colData.push(c);
+        }
+        return colData;
+    }
+
+    /**
+     * Get the display value of a row
+     * @param  {RowElement} DOM element of the row
+     * @return {String}     Usually 'none' or ''
+     */
+    getRowDisplay(row){
+        if(!Types.isObj(row)){
+            return null;
+        }
+        return row.style.display;
+    }
+
+    /**
+     * Validate/invalidate row by setting the 'validRow' attribute on the row
+     * @param  {Number}  rowIndex Index of the row
+     * @param  {Boolean} isValid
+     */
+    validateRow(rowIndex, isValid){
+        let row = this.tbl.rows[rowIndex];
+        if(!row || typeof isValid !== 'boolean'){
+            return;
+        }
+
+        // always visible rows are valid
+        if(this.hasVisibleRows && this.visibleRows.indexOf(rowIndex) !== -1){
+            isValid = true;
+        }
+
+        let displayFlag = isValid ? '' : 'none',
+            validFlag = isValid ? 'true' : 'false';
+        row.style.display = displayFlag;
+
+        if(this.paging){
+            row.setAttribute('validRow', validFlag);
+        }
+    }
+
+    /**
+     * Validate all filterable rows
+     */
+    validateAllRows(){
+        if(!this._hasGrid){
+            return;
+        }
+        this.validRowsIndex = [];
+        for(let k=this.refRow; k<this.nbFilterableRows; k++){
+            this.validateRow(k, true);
+            this.validRowsIndex.push(k);
+        }
+    }
+
+    /**
+     * Set search value to a given filter
+     * @param {Number} index     Column's index
+     * @param {String} searcharg Search term
+     */
+    setFilterValue(index, searcharg=''){
+        if((!this.fltGrid && !this.isFirstLoad) ||
+            !this.getFilterElement(index)){
+            return;
+        }
+        let slc = this.getFilterElement(index),
+            fltColType = this.getFilterType(index);
+
+        if(fltColType !== this.fltTypeMulti &&
+            fltColType != this.fltTypeCheckList){
+            slc.value = searcharg;
+        }
+        //multiple selects
+        else if(fltColType === this.fltTypeMulti){
+            let s = searcharg.split(' '+this.orOperator+' ');
+            // let ct = 0; //keywords counter
+            for(let j=0, len=slc.options.length; j<len; j++){
+                let option = slc.options[j];
+                if(s==='' || s[0]===''){
+                    option.selected = false;
+                }
+                if(option.value===''){
+                    option.selected = false;
+                }
+                if(option.value!=='' &&
+                    Arr.has(s, option.value, true)){
+                    option.selected = true;
+                }//if
+            }//for j
+        }
+        //checklist
+        else if(fltColType === this.fltTypeCheckList){
+            searcharg = Str.matchCase(searcharg, this.caseSensitive);
+            let sarg = searcharg.split(' '+this.orOperator+' ');
+            let lisNb = Dom.tag(slc,'li').length;
+
+            slc.setAttribute('value', '');
+            slc.setAttribute('indexes', '');
+
+            for(let k=0; k<lisNb; k++){
+                let li = Dom.tag(slc,'li')[k],
+                    lbl = Dom.tag(li,'label')[0],
+                    chk = Dom.tag(li,'input')[0],
+                    lblTxt = Str.matchCase(
+                        Dom.getText(lbl), this.caseSensitive);
+                if(lblTxt !== '' && Arr.has(sarg, lblTxt, true)){
+                    chk.checked = true;
+                    this.Mod.checkList.setCheckListValues(chk);
+                }
+                else{
+                    chk.checked = false;
+                    this.Mod.checkList.setCheckListValues(chk);
+                }
+            }
+        }
+    }
+
+    /**
+     * Set them columns' widths as per configuration
+     * @param {Number} rowIndex Optional row index to apply the widths to
+     * @param {Element} tbl DOM element
+     */
+    setColWidths(rowIndex, tbl){
+        if(!this.fltGrid || !this.hasColWidths){
+            return;
+        }
+        tbl = tbl || this.tbl;
+        let rIndex;
+        if(rowIndex===undefined){
+            rIndex = tbl.rows[0].style.display!='none' ? 0 : 1;
+        } else{
+            rIndex = rowIndex;
+        }
+
+        setWidths.call(this);
+
+        function setWidths(){
+            /*jshint validthis:true */
+            let nbCols = this.nbCells;
+            let colWidths = this.colWidths;
+            let colTags = Dom.tag(tbl, 'col');
+            let tblHasColTag = colTags.length > 0;
+            let frag = !tblHasColTag ? doc.createDocumentFragment() : null;
+            for(let k=0; k<nbCols; k++){
+                let col;
+                if(tblHasColTag){
+                    col = colTags[k];
+                } else {
+                    col = Dom.create('col', ['id', this.id+'_col_'+k]);
+                    frag.appendChild(col);
+                }
+                col.style.width = colWidths[k];
+            }
+            if(!tblHasColTag){
+                tbl.insertBefore(frag, tbl.firstChild);
+            }
+        }
+    }
+
+    /**
+     * Makes defined rows always visible
+     */
+    enforceVisibility(){
+        if(!this.hasVisibleRows){
+            return;
+        }
+        for(let i=0, len=this.visibleRows.length; i<len; i++){
+            let row = this.visibleRows[i];
+            //row index cannot be > nrows
+            if(row <= this.nbRows){
+                this.validateRow(row, true);
+            }
+        }
+    }
+
+    clearFilters(){
+        this.EvtManager(this.Evt.name.clear);
+    }
+
+    /**
+     * Clear all the filters' values
+     */
+    _clearFilters(){
+        if(!this.fltGrid){
+            return;
+        }
+        if(this.onBeforeReset){
+            this.onBeforeReset.call(null, this, this.getFiltersValue());
+        }
+        for(let i=0, len=this.fltIds.length; i<len; i++){
+            this.setFilterValue(i, '');
+        }
+        if(this.linkedFilters){
+            this.linkFilters();
+        }
+        if(this.rememberPageLen){ Cookie.remove(this.pgLenCookie); }
+        if(this.rememberPageNb){ Cookie.remove(this.pgNbCookie); }
+        if(this.onAfterReset){ this.onAfterReset.call(null, this); }
+    }
+
+    /**
+     * Clears filtered columns visual indicator (background color)
+     */
+    clearActiveColumns(){
+        for(let i=0, len=this.getCellsNb(this.headersRow); i<len; i++){
+            Dom.removeClass(
+                this.getHeaderElement(i), this.activeColumnsCssClass);
+        }
+    }
+
+    /**
+     * Refresh the filters subject to linking ('select', 'multiple',
+     * 'checklist' type)
+     */
+    linkFilters(){
+        if(!this.activeFilterId){
+            return;
+        }
+        let slcA1 = this.getFiltersByType(this.fltTypeSlc, true),
+            slcA2 = this.getFiltersByType(this.fltTypeMulti, true),
+            slcA3 = this.getFiltersByType(this.fltTypeCheckList, true),
+            slcIndex = slcA1.concat(slcA2);
+        slcIndex = slcIndex.concat(slcA3);
+
+        let activeFlt = this.activeFilterId.split('_')[0];
+        activeFlt = activeFlt.split(this.prfxFlt)[1];
+        let slcSelectedValue;
+        for(let i=0, len=slcIndex.length; i<len; i++){
+            let curSlc = Dom.id(this.fltIds[slcIndex[i]]);
+            slcSelectedValue = this.getFilterValue(slcIndex[i]);
+
+            // Welcome to cyclomatic complexity hell :)
+            // TODO: simplify/refactor if statement
+            if(activeFlt!==slcIndex[i] ||
+                (this.paging && slcA1.indexOf(slcIndex[i]) != -1 &&
+                    activeFlt === slcIndex[i] ) ||
+                (!this.paging && (slcA3.indexOf(slcIndex[i]) != -1 ||
+                    slcA2.indexOf(slcIndex[i]) != -1)) ||
+                slcSelectedValue === this.displayAllText ){
+
+                if(slcA3.indexOf(slcIndex[i]) != -1){
+                    this.Mod.checkList.checkListDiv[slcIndex[i]].innerHTML = '';
+                } else {
+                    curSlc.innerHTML = '';
+                }
+
+                //1st option needs to be inserted
+                if(this.loadFltOnDemand) {
+                    let opt0 = Dom.createOpt(this.displayAllText, '');
+                    if(curSlc){
+                        curSlc.appendChild(opt0);
+                    }
+                }
+
+                if(slcA3.indexOf(slcIndex[i]) != -1){
+                    this.Mod.checkList._build(slcIndex[i]);
+                } else {
+                    this.Mod.dropdown._build(slcIndex[i], true);
+                }
+
+                this.setFilterValue(slcIndex[i], slcSelectedValue);
+            }
+        }// for i
+    }
+
+    /**
+     * Re-generate the filters grid bar when previously removed
+     */
+    _resetGrid(){
+        if(this.isFirstLoad){
+            return;
+        }
+
+        let Mod = this.Mod;
+        let tbl = this.tbl;
+        let rows = tbl.rows;
+        let filtersRowIndex = this.filtersRowIndex;
+        let filtersRow = rows[filtersRowIndex];
+
+        // grid was removed, grid row element is stored in fltGridEl property
+        if(!this.gridLayout){
+            // If table has a thead ensure the filters row is appended in the
+            // thead element
+            if(tbl.tHead){
+                var tempRow = tbl.tHead.insertRow(this.filtersRowIndex);
+                tbl.tHead.replaceChild(this.fltGridEl, tempRow);
+            } else {
+                filtersRow.parentNode.insertBefore(this.fltGridEl, filtersRow);
+            }
+        }
+
+        // filters are appended in external placeholders elements
+        if(this.isExternalFlt){
+            let externalFltTgtIds = this.externalFltTgtIds;
+            for(let ct=0, len=externalFltTgtIds.length; ct<len; ct++){
+                let extFlt = Dom.id(externalFltTgtIds[ct]);
+
+                if(!extFlt){ continue; }
+
+                let externalFltEl = this.externalFltEls[ct];
+                extFlt.appendChild(externalFltEl);
+                let colFltType = this.getFilterType(ct);
+                //IE special treatment for gridLayout, appended filters are
+                //empty
+                if(this.gridLayout &&
+                    externalFltEl.innerHTML === '' &&
+                    colFltType !== this.fltTypeInp){
+                    if(colFltType === this.fltTypeSlc ||
+                        colFltType === this.fltTypeMulti){
+                        Mod.dropdown.build(ct);
+                    }
+                    if(colFltType === this.fltTypeCheckList){
+                        Mod.checkList.build(ct);
+                    }
+                }
+            }
+        }
+
+        this.nbFilterableRows = this.getRowsNb();
+        this.nbVisibleRows = this.nbFilterableRows;
+        this.nbRows = rows.length;
+
+        if(this.popupFilters){
+            this.headersRow++;
+            Mod.popupFilter.reset();
+        }
+
+        if(!this.gridLayout){
+            Dom.addClass(this.tbl, this.prfxTf);
+        }
+        this._hasGrid = true;
+    }
+
+    /**
+     * Determines if passed filter column implements exact query match
+     * @param  {Number}  colIndex [description]
+     * @return {Boolean}          [description]
+     */
+    isExactMatch(colIndex){
+        let fltType = this.getFilterType(colIndex);
+        return this.exactMatchByCol[colIndex] || this.exactMatch ||
+            (fltType!==this.fltTypeInp);
+    }
+
+    /**
+     * Checks if passed data contains the searched arg
+     * @param  {String} arg         Search term
+     * @param  {String} data        Data string
+     * @param  {Boolean} exactMatch Exact match
+     * @return {Boolean]}
+     *
+     * TODO: move into string module, remove fltType in order to decouple it
+     * from TableFilter module
+     */
+    _containsStr(arg, data, exactMatch){
+        // Improved by Cedric Wartel (cwl)
+        // automatic exact match for selects and special characters are now
+        // filtered
+        let regexp,
+            modifier = this.caseSensitive ? 'g' : 'gi';
+        if(exactMatch){
+            regexp = new RegExp(
+                '(^\\s*)'+ Str.rgxEsc(arg) +'(\\s*$)', modifier);
+        } else {
+            regexp = new RegExp(Str.rgxEsc(arg), modifier);
+        }
+        return regexp.test(data);
+    }
+
+    /**
+     * Check if passed script or stylesheet is already imported
+     * @param  {String}  filePath Ressource path
+     * @param  {String}  type     Possible values: 'script' or 'link'
+     * @return {Boolean}
+     */
+    isImported(filePath, type){
+        let imported = false,
+            importType = !type ? 'script' : type,
+            attr = importType == 'script' ? 'src' : 'href',
+            files = Dom.tag(doc, importType);
+        for (let i=0, len=files.length; i<len; i++){
+            if(files[i][attr] === undefined){
+                continue;
+            }
+            if(files[i][attr].match(filePath)){
+                imported = true;
+                break;
+            }
+        }
+        return imported;
+    }
+
+    /**
+     * Import script or stylesheet
+     * @param  {String}   fileId   Ressource ID
+     * @param  {String}   filePath Ressource path
+     * @param  {Function} callback Callback
+     * @param  {String}   type     Possible values: 'script' or 'link'
+     */
+    import(fileId, filePath, callback, type){
+        let ftype = !type ? 'script' : type,
+            imported = this.isImported(filePath, ftype);
+        if(imported){
+            return;
+        }
+        let o = this,
+            isLoaded = false,
+            file,
+            head = Dom.tag(doc, 'head')[0];
+
+        if(Str.lower(ftype) === 'link'){
+            file = Dom.create(
+                'link',
+                ['id', fileId], ['type', 'text/css'],
+                ['rel', 'stylesheet'], ['href', filePath]
+            );
+        } else {
+            file = Dom.create(
+                'script', ['id', fileId],
+                ['type', 'text/javascript'], ['src', filePath]
+            );
+        }
+
+        //Browser <> IE onload event works only for scripts, not for stylesheets
+        file.onload = file.onreadystatechange = function(){
+            if(!isLoaded &&
+                (!this.readyState || this.readyState === 'loaded' ||
+                    this.readyState === 'complete')){
+                isLoaded = true;
+                if(typeof callback === 'function'){
+                    callback.call(null, o);
+                }
+            }
+        };
+        file.onerror = function(){
+            throw new Error('TF script could not load: ' + filePath);
+        };
+        head.appendChild(file);
+    }
+
+    /**
+     * Check if table has filters grid
+     * @return {Boolean}
+     */
+    hasGrid(){
+        return this._hasGrid;
+    }
+
+    /**
+     * Get list of filter IDs
+     * @return {[type]} [description]
+     */
+    getFiltersId(){
+        return this.fltIds || [];
+    }
+
+    /**
+     * Get filtered (valid) rows indexes
+     * @param  {Boolean} reCalc Force calculation of filtered rows list
+     * @return {Array}          List of row indexes
+     */
+    getValidRows(reCalc){
+        if(!reCalc){
+            return this.validRowsIndex;
+        }
+
+        this.validRowsIndex = [];
+        for(let k=this.refRow; k<this.getRowsNb(true); k++){
+            let r = this.tbl.rows[k];
+            if(!this.paging){
+                if(this.getRowDisplay(r) !== 'none'){
+                    this.validRowsIndex.push(r.rowIndex);
+                }
+            } else {
+                if(r.getAttribute('validRow') === 'true' ||
+                    r.getAttribute('validRow') === null){
+                    this.validRowsIndex.push(r.rowIndex);
+                }
+            }
+        }
+        return this.validRowsIndex;
+    }
+
+    /**
+     * Get the index of the row containing the filters
+     * @return {Number}
+     */
+    getFiltersRowIndex(){
+        return this.filtersRowIndex;
+    }
+
+    /**
+     * Get the index of the headers row
+     * @return {Number}
+     */
+    getHeadersRowIndex(){
+        return this.headersRow;
+    }
+
+    /**
+     * Get the row index from where the filtering process start (1st filterable
+     * row)
+     * @return {Number}
+     */
+    getStartRowIndex(){
+        return this.refRow;
+    }
+
+    /**
+     * Get the index of the last row
+     * @return {Number}
+     */
+    getLastRowIndex(){
+        if(!this._hasGrid){
+            return;
+        }
+        return (this.nbRows-1);
+    }
+
+    /**
+     * Get the header DOM element for a given column index
+     * @param  {Number} colIndex Column index
+     * @return {Object}
+     */
+    getHeaderElement(colIndex){
+        let table = this.gridLayout ? this.Mod.gridLayout.headTbl : this.tbl;
+        let tHead = Dom.tag(table, 'thead');
+        let headersRow = this.headersRow;
+        let header;
+        for(let i=0; i<this.nbCells; i++){
+            if(i !== colIndex){
+                continue;
+            }
+            if(tHead.length === 0){
+                header = table.rows[headersRow].cells[i];
+            }
+            if(tHead.length === 1){
+                header = tHead[0].rows[headersRow].cells[i];
+            }
+            break;
+        }
+        return header;
+    }
+
+    /**
+     * Return the list of headers' text
+     * @return {Array} list of headers' text
+     */
+    getHeadersText(){
+        let headers = [];
+        for(let j=0; j<this.nbCells; j++){
+            let header = this.getHeaderElement(j);
+            let headerText = Dom.getText(header);
+            headers.push(headerText);
+        }
+        return headers;
+    }
+
+    /**
+     * Return the filter type for a specified column
+     * @param  {Number} colIndex Column's index
+     * @return {String}
+     */
+    getFilterType(colIndex){
+        let colType = this.cfg['col_'+colIndex];
+        return !colType ? this.fltTypeInp : Str.lower(colType);
+    }
+
+    /**
+     * Get the total number of filterable rows
+     * @return {Number}
+     */
+    getFilterableRowsNb(){
+        return this.getRowsNb(false);
+    }
+
+    /**
+     * Get the configuration object (literal object)
+     * @return {Object}
+     */
+    config(){
+        return this.cfg;
+    }
+}
diff --git a/missing-sources/tablefilter/types.js b/missing-sources/tablefilter/types.js
new file mode 100644 (file)
index 0000000..771ff48
--- /dev/null
@@ -0,0 +1,71 @@
+/**
+ * Types utilities
+ */
+
+const UNDEFINED = void 0;
+
+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 if argument is a function
+     * @param  {Function} fn
+     * @return {Boolean}
+     */
+    isFn(fn){
+        return (fn && fn.constructor == Function);
+    },
+
+    /**
+     * Check if argument is an array
+     * @param  {Array}  obj
+     * @return {Boolean}
+     */
+    isArray(obj){
+        return (obj && obj.constructor == Array);
+    },
+
+    /**
+     * Determine if argument is undefined
+     * @param  {Any}  o
+     * @return {Boolean}
+     */
+    isUndef(o){
+        return  o === UNDEFINED;
+    },
+
+    /**
+     * Determine if argument is null
+     * @param  {Any}  o
+     * @return {Boolean}
+     */
+    isNull(o){
+        return o === null;
+    },
+
+    /**
+     * 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;
+    }
+};
diff --git a/patches/Disable-test_regression_rP26522.patch b/patches/Disable-test_regression_rP26522.patch
new file mode 100644 (file)
index 0000000..18b17d6
--- /dev/null
@@ -0,0 +1,42 @@
+Description: Disable a failing regression test
+ In TestCGUIText::test_regression_rP26522:
+ ./source/gui/tests/test_CGUIText.h:319: Error: Expected ((g_VFS->Mount(L"", DataDir() / "mods" / "mod" / "", VFS_MOUNT_MUST_EXIST)) == INFO::OK), found (-110100 != 0)
+ ERROR: Failed to open font file fonts/sans-bold-13.fnt
+ ERROR: Failed to open font file fonts/sans-10.fnt
+ ./source/gui/tests/test_CGUIText.h:332: Error: Expected (text.GetSize().Height == 14 + 9 + 8 * 2), found (22.0000 != 39)
+ .............................................................................................................................................................................................................................................Skipping globalscripts tests (can't find binaries/data/mods/public/globalscripts/tests/)
+ .Skipping component scripts tests (can't find binaries/data/mods/public/simulation/components/tests/setup.js)
+ ................................................................................................................
+ Failed 1 and Skipped 0 of 391 tests
+ Success rate: 99%
+Author: Ludovic Rousseau <rousseau@debian.org>
+Forwarded: https://trac.wildfiregames.com/ticket/6630
+Last-Update: 2022-10-02
+
+--- a/source/gui/tests/test_CGUIText.h
++++ b/source/gui/tests/test_CGUIText.h
+@@ -314,24 +314,6 @@
+               TS_ASSERT_EQUALS(text.GetSize().Height, lineHeight + padding * 2);
+       }
+-      void test_regression_rP26522()
+-      {
+-              TS_ASSERT_OK(g_VFS->Mount(L"", DataDir() / "mods" / "mod" / "", VFS_MOUNT_MUST_EXIST));
+-
+-              CGUI gui(g_ScriptContext);
+-
+-              const CStrW font = L"sans-bold-13";
+-              CGUIString string;
+-              CGUIText text;
+-
+-              // rP26522 introduced a bug that triggered in rare cases with word-wrapping.
+-              string.SetValue(L"90–120 min");
+-              text = CGUIText(gui, string, L"sans-bold-13", 53, 8.f, EAlign::LEFT, nullptr);
+-
+-              TS_ASSERT_EQUALS(text.GetTextCalls().size(), 2);
+-              TS_ASSERT_EQUALS(text.GetSize().Height, 14 + 9 + 8 * 2);
+-      }
+-
+       void test_multiple_blank_spaces()
+       {
+               CGUI gui(g_ScriptContext);
diff --git a/patches/Fix-build-mozjs-on-armhf.patch b/patches/Fix-build-mozjs-on-armhf.patch
new file mode 100644 (file)
index 0000000..9476ce1
--- /dev/null
@@ -0,0 +1,75 @@
+From: Shengjing Zhu <zhsj@debian.org>
+Date: Thu, 23 Dec 2021 01:31:00 +0800
+Subject: Fix build mozjs on armhf
+
+---
+ .../Fix-armhf-build-for-GCC-11-627.patch           | 45 ++++++++++++++++++++++
+ libraries/source/spidermonkey/patch.sh             |  3 ++
+ 2 files changed, 48 insertions(+)
+ create mode 100644 libraries/source/spidermonkey/Fix-armhf-build-for-GCC-11-627.patch
+
+diff --git a/libraries/source/spidermonkey/Fix-armhf-build-for-GCC-11-627.patch b/libraries/source/spidermonkey/Fix-armhf-build-for-GCC-11-627.patch
+new file mode 100644
+index 0000000..6d9364a
+--- /dev/null
++++ b/libraries/source/spidermonkey/Fix-armhf-build-for-GCC-11-627.patch
+@@ -0,0 +1,45 @@
++From 9b97780f1b2f936d960b74b8f7a3b851f607a0f4 Mon Sep 17 00:00:00 2001
++From: Ximin Luo <infinity0@pwned.gg>
++Date: Thu, 14 Oct 2021 15:23:48 +0100
++Subject: [PATCH] Fix armhf build for GCC-11 (#627)
++
++GCC 11 changed the default flag to put the FP option in the -march flag (arm7-a+fp) rather than a specific FPU flag. Therefore it needs to be given separately now.
++
++See https://bugs.launchpad.net/ubuntu/+source/gcc-defaults/+bug/1939379 for some more details.
++
++Origin: https://github.com/alexcrichton/cc-rs/commit/b2f6b146b75299c444e05bbde50d03705c7c4b6e
++Forwarded: https://github.com/alexcrichton/cc-rs/pull/627
++Bug-Debian: https://bugs.debian.org/1001314
++---
++ third_party/rust/cc/.cargo-checksum.json | 2 +-
++ third_party/rust/cc/src/lib.rs           | 5 +++++
++ 2 files changed, 6 insertions(+), 1 deletion(-)
++
++diff --git a/third_party/rust/cc/.cargo-checksum.json b/third_party/rust/cc/.cargo-checksum.json
++index 417fde795..43992977a 100644
++--- a/third_party/rust/cc/.cargo-checksum.json
+++++ b/third_party/rust/cc/.cargo-checksum.json
++@@ -1 +1 @@
++-{"files":{"Cargo.lock":"3aff5f8b0a7f4d72852b11b0526f0002e6bf55f19f1ebd6470d7f97fbd540e60","Cargo.toml":"6ab10d9b6a9c6f0909074e6698c90c6b6a7223661ec2e83174d2593117cbe7f2","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"7184fbdf375a057e673257348f6d7584c0dd11b66318d98f3647f69eb610b097","src/bin/gcc-shim.rs":"b77907875029494b6288841c3aed2e4939ed40708c7f597fca5c9e2570490ca6","src/com.rs":"bcdaf1c28b71e6ef889c6b08d1ce9d7c0761344a677f523bc4c3cd297957f804","src/lib.rs":"4753929dbb7b676c19d7cfa06d0a47e37003554b80c536cbf2b892d591ef61c2","src/registry.rs":"3cc1b5a50879fa751572878ae1d0afbfc960c11665258492754b2c8bccb0ff5d","src/setup_config.rs":"7014103587d3382eac599cb76f016e2609b8140970861b2237982d1db24af265","src/winapi.rs":"ea8b7edbb9ff87957254f465c2334e714c5d6b3b19a8d757c48ea7ca0881c50c","src/windows_registry.rs":"388e79dcf3e84078ae0b086c6cdee9cf9eb7e3ffafdcbf3e2df26163661f5856","tests/cc_env.rs":"e02b3b0824ad039b47e4462c5ef6dbe6c824c28e7953af94a0f28f7b5158042e","tests/cflags.rs":"57f06eb5ce1557e5b4a032d0c4673e18fbe6f8d26c1deb153126e368b96b41b3","tests/cxxflags.rs":"c2c6c6d8a0d7146616fa1caed26876ee7bc9fcfffd525eb4743593cade5f3371","tests/support/mod.rs":"71620b178583b6e6e5e0d4cac14e2cef6afc62fb6841e0c72ed1784543abf8ac","tests/test.rs":"1605640c9b94a77f48fc92e1dc0485bdf1960da5626e2e00279e4703691656bc"},"package":"aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8"}
++\ No newline at end of file
+++{"files":{"Cargo.lock":"3aff5f8b0a7f4d72852b11b0526f0002e6bf55f19f1ebd6470d7f97fbd540e60","Cargo.toml":"6ab10d9b6a9c6f0909074e6698c90c6b6a7223661ec2e83174d2593117cbe7f2","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"7184fbdf375a057e673257348f6d7584c0dd11b66318d98f3647f69eb610b097","src/bin/gcc-shim.rs":"b77907875029494b6288841c3aed2e4939ed40708c7f597fca5c9e2570490ca6","src/com.rs":"bcdaf1c28b71e6ef889c6b08d1ce9d7c0761344a677f523bc4c3cd297957f804","src/lib.rs":"bb2df0dc5fd2acf8c474bab64080bd374cca9b3568aaf16cfe8fc6b5253fdeb3","src/registry.rs":"3cc1b5a50879fa751572878ae1d0afbfc960c11665258492754b2c8bccb0ff5d","src/setup_config.rs":"7014103587d3382eac599cb76f016e2609b8140970861b2237982d1db24af265","src/winapi.rs":"ea8b7edbb9ff87957254f465c2334e714c5d6b3b19a8d757c48ea7ca0881c50c","src/windows_registry.rs":"388e79dcf3e84078ae0b086c6cdee9cf9eb7e3ffafdcbf3e2df26163661f5856","tests/cc_env.rs":"e02b3b0824ad039b47e4462c5ef6dbe6c824c28e7953af94a0f28f7b5158042e","tests/cflags.rs":"57f06eb5ce1557e5b4a032d0c4673e18fbe6f8d26c1deb153126e368b96b41b3","tests/cxxflags.rs":"c2c6c6d8a0d7146616fa1caed26876ee7bc9fcfffd525eb4743593cade5f3371","tests/support/mod.rs":"71620b178583b6e6e5e0d4cac14e2cef6afc62fb6841e0c72ed1784543abf8ac","tests/test.rs":"1605640c9b94a77f48fc92e1dc0485bdf1960da5626e2e00279e4703691656bc"},"package":"aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8"}
++\ No newline at end of file
++diff --git a/third_party/rust/cc/src/lib.rs b/third_party/rust/cc/src/lib.rs
++index 621d31d6b..f6a3f750c 100644
++--- a/third_party/rust/cc/src/lib.rs
+++++ b/third_party/rust/cc/src/lib.rs
++@@ -1457,6 +1457,11 @@ impl Build {
++                     && target.contains("-linux-")
++                 {
++                     cmd.args.push("-march=armv7-a".into());
+++
+++                    if target.ends_with("eabihf") {
+++                        // lowest common denominator FPU
+++                        cmd.args.push("-mfpu=vfpv3-d16".into());
+++                    }
++                 }
++ 
++                 // (x86 Android doesn't say "eabi")
++-- 
++2.34.1
++
+diff --git a/libraries/source/spidermonkey/patch.sh b/libraries/source/spidermonkey/patch.sh
+index 879964b..9059fd6 100644
+--- a/libraries/source/spidermonkey/patch.sh
++++ b/libraries/source/spidermonkey/patch.sh
+@@ -43,6 +43,9 @@ patch -p1 < ../FixRpiUnalignedFpAccess.diff
+ # Note that this isn't quite the upstream patch to match our version.
+ patch -p1 < ../FixRust150.diff
++# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1001882
++patch -p1 < ../Fix-armhf-build-for-GCC-11-627.patch
++
+ # Patch those separately, as they might interfere with normal behaviour.
+ if [ "$(uname -s)" = "FreeBSD" ];
+ then
diff --git a/patches/TestStunClient b/patches/TestStunClient
new file mode 100644 (file)
index 0000000..9404b46
--- /dev/null
@@ -0,0 +1,90 @@
+Description: remove this test since it fails in the build environment
+ The failure is:
+ In TestStunClient::test_local_ip:
+ ./source/network/tests/test_StunClient.h:43: Error: Assertion failed: StunClient::FindLocalIP(ip)
+ ./source/network/tests/test_StunClient.h:47: Error: Test failed: StunClient::FindLocalIP did not return a valid IPV4 address: wrong size
+Author: Ludovic Rousseau <rousseau@debian.org>
+Last-Update: 2021-08-27
+
+--- a/source/network/tests/test_StunClient.h
++++ /dev/null
+@@ -1,79 +0,0 @@
+-/* Copyright (C) 2021 Wildfire Games.
+- * This file is part of 0 A.D.
+- *
+- * 0 A.D. is free software: you can redistribute it and/or modify
+- * it under the terms of the GNU General Public License as published by
+- * the Free Software Foundation, either version 2 of the License, or
+- * (at your option) any later version.
+- *
+- * 0 A.D. is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+- * GNU General Public License for more details.
+- *
+- * You should have received a copy of the GNU General Public License
+- * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
+- */
+-
+-#include "lib/self_test.h"
+-
+-#include "network/StunClient.h"
+-
+-#include "lib/external_libraries/enet.h"
+-#include "ps/ConfigDB.h"
+-#include "ps/CStr.h"
+-
+-class TestStunClient : public CxxTest::TestSuite
+-{
+-public:
+-      void setUp()
+-      {
+-              // Sets networking up in a cross-platform manner.
+-              enet_initialize();
+-      }
+-
+-      void tearDown()
+-      {
+-              enet_deinitialize();
+-      }
+-
+-      void test_local_ip()
+-      {
+-              CStr ip;
+-              TS_ASSERT(StunClient::FindLocalIP(ip));
+-              // Quick validation that this looks like a valid IP address.
+-              if (ip.size() < 8 || ip.size() > 15)
+-              {
+-                      TS_FAIL("StunClient::FindLocalIP did not return a valid IPV4 address: wrong size");
+-                      return;
+-              }
+-              int dots = 0;
+-              for (char c : ip)
+-              {
+-                      if (c == '.')
+-                              ++dots;
+-                      else if (c < '0' && c > '9')
+-                      {
+-                              TS_FAIL("StunClient::FindLocalIP did not return a valid IPV4 address: wrong character");
+-                              return;
+-                      }
+-              }
+-              if (dots != 3)
+-                      TS_FAIL("StunClient::FindLocalIP did not return a valid IPV4 address: wrong separators");
+-      }
+-
+-      void test_stun_DISABLED()
+-      {
+-              // Disabled test -> should return your external IP by connecting to our STUN server.
+-              CConfigDB::Initialise();
+-              CStr ip;
+-              u16 port;
+-              g_ConfigDB.SetValueString(CFG_COMMAND, "lobby.stun.server", "lobby.wildfiregames.com");
+-              g_ConfigDB.SetValueString(CFG_COMMAND, "lobby.stun.port", "3478");
+-              ENetAddress addr { ENET_HOST_ANY, ENET_PORT_ANY };
+-              ENetHost* host = enet_host_create(&addr, 1, 1, 0, 0);
+-              StunClient::FindPublicIP(*host, ip, port);
+-              LOGWARNING("%s %i", ip.c_str(), port);
+-              CConfigDB::Shutdown();
+-      }
+-};
diff --git a/patches/allow-build-with-root.patch b/patches/allow-build-with-root.patch
new file mode 100644 (file)
index 0000000..076b524
--- /dev/null
@@ -0,0 +1,18 @@
+Description: Allow building 0ad as root/fakeroot
+Forwarded: not-needed
+Author: Rico Tzschichholz <ricotz@ubuntu.com>
+Last-Update: 2014-10-13
+
+--- a/build/workspaces/update-workspaces.sh
++++ b/build/workspaces/update-workspaces.sh
+@@ -1,10 +1,5 @@
+ #!/bin/sh
+-if [ "$(id -u)" = "0" ]; then
+-   echo "Running as root will mess up file permissions. Aborting ..." 1>&2
+-   exit 1
+-fi
+-
+ die()
+ {
+   echo ERROR: $*
diff --git a/patches/fix-bindir.patch b/patches/fix-bindir.patch
new file mode 100644 (file)
index 0000000..a9eb42a
--- /dev/null
@@ -0,0 +1,21 @@
+Description: Fix search path for pyrogenesis
+ Modify launcher script to execute /usr/games/pyrogenesis if /usr/games is
+ not in the user's $PATH.
+Forwarded: http://trac.wildfiregames.com/ticket/1424
+Author: Vincent Cheng <vcheng@debian.org>
+Bug-Debian: https://bugs.debian.org/679033
+Bug-Ubuntu: https://bugs.launchpad.net/bugs/1380737
+Last-Update: 2014-10-13
+
+--- a/build/resources/0ad.sh
++++ b/build/resources/0ad.sh
+@@ -3,6 +3,9 @@
+ pyrogenesis=$(which pyrogenesis 2> /dev/null)
+ if [ -x "$pyrogenesis" ] ; then
+   "$pyrogenesis" "$@"
++elif [ -x /usr/games/pyrogenesis ] ; then
++  # Fallback in case /usr/games is not in $PATH; see #679033 and LP: #1380737
++  /usr/games/pyrogenesis "$@"
+ else
+   echo "Error: pyrogenesis not found in ($PATH)"
+   exit 1
diff --git a/patches/series b/patches/series
new file mode 100644 (file)
index 0000000..f91feef
--- /dev/null
@@ -0,0 +1,5 @@
+TestStunClient
+allow-build-with-root.patch
+fix-bindir.patch
+Fix-build-mozjs-on-armhf.patch
+Disable-test_regression_rP26522.patch
diff --git a/pyrogenesis.6 b/pyrogenesis.6
new file mode 100644 (file)
index 0000000..513070f
--- /dev/null
@@ -0,0 +1 @@
+.so man6/0ad.6
diff --git a/rules b/rules
new file mode 100755 (executable)
index 0000000..60488d7
--- /dev/null
+++ b/rules
@@ -0,0 +1,63 @@
+#!/usr/bin/make -f
+
+# mozjs' build process does not seem to be compatible with other shells
+# like zsh
+export SHELL = /bin/sh
+
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+DPKG_EXPORT_BUILDFLAGS = 1
+include /usr/share/dpkg/buildflags.mk
+
+include /usr/share/dpkg/architecture.mk
+
+PARALLEL_JOBS=$(shell getconf _NPROCESSORS_ONLN)
+ifeq ($(findstring parallel=,$(DEB_BUILD_OPTIONS)),)
+       export DEB_BUILD_OPTIONS+=parallel=$(PARALLEL_JOBS)
+endif
+
+%:
+       dh $@
+
+override_dh_auto_clean:
+       build/workspaces/clean-workspaces.sh
+       # Clean up some extra cruft not picked up by clean-workspaces.sh
+       find binaries/system/ -type f ! -name readme.txt -delete
+       rm -f libraries/fcollada/lib/*.a
+       rm -f build/premake/.*.tmp
+       rm -rf libraries/source/spidermonkey/lib
+       rm -f libraries/source/cxxtest-4.4/python/cxxtest/*.pyc
+       rm -f libraries/source/fcollada/lib/*
+       rm -rf libraries/source/spidermonkey/include-unix-*
+       rm -rf libraries/source/spidermonkey/mozjs-78.6.0
+       rm -f libraries/source/nvtt/lib/*.so
+       rm -f source/ps/tests/stub_impl_hack.cpp
+       dh_auto_clean
+
+override_dh_auto_build:
+       mkdir -p libraries/source/fcollada/lib
+       build/workspaces/update-workspaces.sh \
+               --bindir=/usr/games \
+               --libdir=/usr/lib/games/0ad \
+               --datadir=/usr/share/games/0ad \
+               -j$(PARALLEL_JOBS)
+       
+       $(MAKE) config=release verbose=1 -C build/workspaces/gcc \
+               -j$(PARALLEL_JOBS)
+
+override_dh_auto_test:
+       # Note: Avoid running tests from root dir of build, otherwise certain
+       # tests (i.e. in testsuite MeshManager) may not work as intended and
+       # create spurious directories above root dir (../data/mods).
+ifeq (,$(filter armhf arm64 kfreebsd-amd64 kfreebsd-i386, $(DEB_HOST_ARCH)))
+       cd binaries/system/ && LD_LIBRARY_PATH=. ./test -libdir .
+endif
+
+override_dh_auto_install:
+       install -Dm 0755 build/resources/0ad.sh $(CURDIR)/debian/tmp/usr/games/0ad
+       dh_auto_install
+
+override_dh_makeshlibs:
+       dh_makeshlibs -Xusr/lib/games/0ad
+
+override_dh_dwz:
+       dh_dwz -Xlibmozjs78-ps-release.so
diff --git a/source/format b/source/format
new file mode 100644 (file)
index 0000000..163aaf8
--- /dev/null
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/source/lintian-overrides b/source/lintian-overrides
new file mode 100644 (file)
index 0000000..28a75fb
--- /dev/null
@@ -0,0 +1,31 @@
+# Not a problem since 0ad and 0ad-data are uploaded at the same time, and
+# dpkg-gencontrol doesn't provide any other substitution variables for packages
+# to depend on versioned packages built from a separate source package
+0ad source: version-substvar-for-external-package
+# Source for the following files can be found in debian/missing-sources/
+# (they trigger lintian errors because they are not named the same as the
+# non-minified files in debian/missing-sources/, or contain inline
+# javascript dependencies). See debian/missing-sources/README.txt for details.
+0ad source: very-long-line-length-in-source-file source/tools/replayprofile/jquery.flot.js line length is 2981 characters (>512)
+0ad source: source-contains-prebuilt-javascript-object source/tools/replayprofile/jquery.flot.js line length is 2981 characters (>512)
+0ad source: source-is-missing [source/tools/replayprofile/jquery.flot.js]
+0ad source: very-long-line-length-in-source-file source/tools/replayprofile/jquery.flot.navigate.js line length is 1984 characters (>512)
+0ad source: source-contains-prebuilt-javascript-object source/tools/replayprofile/jquery.flot.navigate.js line length is 1984 characters (>512)
+0ad source: source-is-missing [source/tools/replayprofile/jquery.flot.navigate.js]
+0ad source: very-long-line-length-in-source-file source/tools/templatesanalyzer/tablefilter/tablefilter.js line length is 32005 characters (>512)
+0ad source: source-contains-prebuilt-javascript-object source/tools/templatesanalyzer/tablefilter/tablefilter.js line length is 32005 characters (>512)
+0ad source: source-is-missing [source/tools/templatesanalyzer/tablefilter/tablefilter.js]
+0ad source: very-long-line-length-in-source-file source/tools/templatesanalyzer/tablefilter/tf-1.js line length is 32004 characters (>512)
+0ad source: source-contains-prebuilt-javascript-object source/tools/templatesanalyzer/tablefilter/tf-1.js line length is 32004 characters (>512)
+0ad source: source-is-missing [source/tools/templatesanalyzer/tablefilter/tf-1.js]
+
+## Currently unused overrides/comments:
+# These files actually are non-minified source code. Silly lintian assumes
+# that all javascript files with lines that have more than 512 characters must
+# be minified javascript files, which is a faulty assumption here.
+#
+# This is where the lintian source-is-missing tag suggests putting non-minified
+# files in, in place of files in the source tarball that only have minified
+# versions. I know for a fact that none of the javascript files in this
+# directory are minified (lintian is just being stupid here...).
+#0ad source: source-is-missing debian/missing-sources/*.js
diff --git a/upstream/metadata b/upstream/metadata
new file mode 100644 (file)
index 0000000..3ff486b
--- /dev/null
@@ -0,0 +1,7 @@
+Name: 0 A.D.
+Bug-Database: https://trac.wildfiregames.com/report
+Contact: https://play0ad.com/about/contact-us/
+Documentation: https://trac.wildfiregames.com/wiki
+FAQ: https://trac.wildfiregames.com/wiki
+Repository: https://svn.wildfiregames.com/public/ps/trunk/
+Repository-Browse: https://trac.wildfiregames.com/browser
diff --git a/watch b/watch
new file mode 100644 (file)
index 0000000..8c326a3
--- /dev/null
+++ b/watch
@@ -0,0 +1,2 @@
+version=4
+https://releases.wildfiregames.com/0ad-([\d\.]+)-.*-unix-build\.tar\.gz