--- /dev/null
--- /dev/null
++.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.
--- /dev/null
--- /dev/null
++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
--- /dev/null
--- /dev/null
++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 (= 12),
++ 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),
++ 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.5.0
++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.
--- /dev/null
--- /dev/null
++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-2021 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/Containers.h
++ libraries/source/nvtt/src/src/nvcore/Debug.cpp
++ libraries/source/nvtt/src/src/nvcore/Debug.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/Library.h
++ libraries/source/nvtt/src/src/nvcore/Memory.cpp
++ libraries/source/nvtt/src/src/nvcore/Memory.h
++ libraries/source/nvtt/src/src/nvcore/Prefetch.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/StrLib.cpp
++ libraries/source/nvtt/src/src/nvcore/StrLib.h
++ libraries/source/nvtt/src/src/nvcore/Stream.h
++ libraries/source/nvtt/src/src/nvcore/TextReader.cpp
++ libraries/source/nvtt/src/src/nvcore/TextReader.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/Tokenizer.cpp
++ libraries/source/nvtt/src/src/nvcore/Tokenizer.h
++ libraries/source/nvtt/src/src/nvcore/Utils.h
++ libraries/source/nvtt/src/src/nvcore/nvcore.h
++ 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/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/HoleFilling.cpp
++ libraries/source/nvtt/src/src/nvimage/HoleFilling.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/NormalMipmap.cpp
++ libraries/source/nvtt/src/src/nvimage/NormalMipmap.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/nvimage.h
++ libraries/source/nvtt/src/src/nvmath/Basis.cpp
++ libraries/source/nvtt/src/src/nvmath/Basis.h
++ libraries/source/nvtt/src/src/nvmath/Box.cpp
++ libraries/source/nvtt/src/src/nvmath/Box.h
++ 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/Matrix.h
++ libraries/source/nvtt/src/src/nvmath/Matrix.inl
++ libraries/source/nvtt/src/src/nvmath/Montecarlo.cpp
++ libraries/source/nvtt/src/src/nvmath/Montecarlo.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/Quaternion.h
++ libraries/source/nvtt/src/src/nvmath/Random.cpp
++ libraries/source/nvtt/src/src/nvmath/Random.h
++ 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/Triangle.cpp
++ libraries/source/nvtt/src/src/nvmath/Triangle.h
++ libraries/source/nvtt/src/src/nvmath/Vector.h
++ libraries/source/nvtt/src/src/nvmath/Vector.inl
++ libraries/source/nvtt/src/src/nvmath/ftoi.h
++ libraries/source/nvtt/src/src/nvmath/nvmath.h
++ 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
++
++Files: libraries/source/valgrind/*
++Copyright: 2003-2012 Josef Weidendorfer
++ 2000-2012 Julian Seward
++License: BSD-4-clause
++
++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".
++
++License: BSD-4-clause
++ Redistribution and use in source and binary forms, with or without
++ modification, are permitted provided that the following conditions
++ are met:
++ .
++ 1. Redistributions of source code must retain the above copyright
++ notice, this list of conditions and the following disclaimer.
++ .
++ 2. The origin of this software must not be misrepresented; you must
++ not claim that you wrote the original software. If you use this
++ software in a product, an acknowledgment in the product
++ documentation would be appreciated but is not required.
++ .
++ 3. Altered source versions must be plainly marked as such, and must
++ not be misrepresented as being the original software.
++ .
++ 4. The name of the author may not be used to endorse or promote
++ products derived from this software without specific prior written
++ permission.
++ .
++ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
--- /dev/null
--- /dev/null
++README.txt
--- /dev/null
--- /dev/null
++#!/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
--- /dev/null
--- /dev/null
++debian/0ad.6
++debian/pyrogenesis.6
--- /dev/null
--- /dev/null
++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)
++
++
--- /dev/null
--- /dev/null
++/* 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]
++ };
++})();
--- /dev/null
--- /dev/null
++/*! \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
--- /dev/null
--- /dev/null
++/*! 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);
--- /dev/null
--- /dev/null
++/**
++ * 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;
++ }
++};
--- /dev/null
--- /dev/null
++/**
++ * 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];
++ }
++
++};
--- /dev/null
--- /dev/null
++/**
++ * 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;
++}
--- /dev/null
--- /dev/null
++/**
++ * 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;
++}
--- /dev/null
--- /dev/null
++/**
++ * 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));
++ }
++};
--- /dev/null
--- /dev/null
++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;
++ }
++}
--- /dev/null
--- /dev/null
++import AdapterEzEditTable from './adapterEzEditTable';
++
++export default AdapterEzEditTable;
--- /dev/null
--- /dev/null
++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(){}
++
++}
--- /dev/null
--- /dev/null
++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▼';
++ //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' : '';
++ }
++ }
++ }
++
++}
--- /dev/null
--- /dev/null
++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;
++ }
++
++}
--- /dev/null
--- /dev/null
++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;
++ }
++}
--- /dev/null
--- /dev/null
++// import 'script!sortabletable';
++import AdapterSortableTable from './adapterSortabletable';
++
++if(!window.SortableTable){
++ require('script!sortabletable');
++}
++
++export default AdapterSortableTable;
--- /dev/null
--- /dev/null
++/**
++ * 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;
++ }
++};
--- /dev/null
--- /dev/null
++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;
++ }
++
++}
--- /dev/null
--- /dev/null
++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);
++ }
++ }
++ }
++}
--- /dev/null
--- /dev/null
++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;
++ }
++}
--- /dev/null
--- /dev/null
++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;
++ }
++
++}
--- /dev/null
--- /dev/null
++
++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;
++ }
++}
--- /dev/null
--- /dev/null
++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;
++ }
++}
--- /dev/null
--- /dev/null
++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><</b>, <b><=</b>, <b>></b>, ' +
++ '<b>>=</b>, <b>=</b>, <b>*</b>, <b>!</b>, <b>{</b>, <b>}</b>, ' +
++ '<b>||</b>,<b>&&</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>©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;
++ }
++
++}
--- /dev/null
--- /dev/null
++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 = [];
++ }
++}
--- /dev/null
--- /dev/null
++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;
++ }
++}
--- /dev/null
--- /dev/null
++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;
++ }
++}
--- /dev/null
--- /dev/null
++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;
++ }
++
++}
--- /dev/null
--- /dev/null
++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;
++ }
++}
--- /dev/null
--- /dev/null
++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;
++ }
++
++}
--- /dev/null
--- /dev/null
++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);
++ }
++
++}
--- /dev/null
--- /dev/null
++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));
++ }
++};
--- /dev/null
--- /dev/null
++/**
++ * 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;
++ }
++
++};
--- /dev/null
--- /dev/null
++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;
++ }
++}
--- /dev/null
--- /dev/null
++/**
++ * 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;
++ }
++};
--- /dev/null
--- /dev/null
++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
--- /dev/null
--- /dev/null
++Description: remove this test since it fails in the build environement
++ 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();
++- }
++-};
--- /dev/null
--- /dev/null
++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: $*
--- /dev/null
--- /dev/null
++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
--- /dev/null
--- /dev/null
++From: Vladislav Belov <vladislavbelov>
++Date: Thu, 3 Mar 2022 19:10:05 +0100
++Subject: Replaces M_PIf by M_PI in Atlas,
++ fixes compilation with gcc 11.2.0 and glibc 2.35.
++
++There was added a workaround in glibc to fix tests.
++
++Refs:
++https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103735
++https://sourceware.org/bugzilla/show_bug.cgi?id=28713
++
++Origin: upstream, commit:https://trac.wildfiregames.com/changeset/26536
++Bug-Debian: https://bugs.debian.org/1008075
++Bug-RedHat: https://bugzilla.redhat.com/show_bug.cgi?id=2045149
++---
++ .../Sections/Environment/Environment.cpp | 88 ++++++++++++++--------
++ 1 file changed, 57 insertions(+), 31 deletions(-)
++
++diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp
++index d4796ec..2cc2652 100644
++--- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp
+++++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp
++@@ -1,4 +1,4 @@
++-/* Copyright (C) 2021 Wildfire Games.
+++/* Copyright (C) 2022 Wildfire Games.
++ * This file is part of 0 A.D.
++ *
++ * 0 A.D. is free software: you can redistribute it and/or modify
++@@ -29,8 +29,6 @@ using AtlasMessage::Shareable;
++
++ static Observable<AtlasMessage::sEnvironmentSettings> g_EnvironmentSettings;
++
++-const float M_PIf = 3.14159265f;
++-
++ //////////////////////////////////////////////////////////////////////////
++
++ class VariableSliderBox : public wxPanel
++@@ -85,12 +83,15 @@ public:
++ : wxPanel(parent),
++ m_Var(var)
++ {
++- m_Conn = g_EnvironmentSettings.RegisterObserver(0, &VariableListBox::OnSettingsChange, this);
+++ m_Conn = g_EnvironmentSettings.RegisterObserver(
+++ 0, &VariableListBox::OnSettingsChange, this);
++
++ m_Sizer = new wxStaticBoxSizer(wxVERTICAL, this, label);
++ SetSizer(m_Sizer);
++
++- m_Combo = new wxComboBox(this, -1, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxArrayString(), wxCB_READONLY),
+++ m_Combo = new wxComboBox(
+++ this, -1, wxEmptyString, wxDefaultPosition, wxDefaultSize,
+++ wxArrayString(), wxCB_READONLY),
++ m_Sizer->Add(m_Combo, wxSizerFlags().Expand());
++ }
++
++@@ -206,8 +207,9 @@ static void SendToGame(const AtlasMessage::sEnvironmentSettings& settings)
++ POST_COMMAND(SetEnvironmentSettings, (settings));
++ }
++
++-EnvironmentSidebar::EnvironmentSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
++-: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer)
+++EnvironmentSidebar::EnvironmentSidebar(
+++ ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
+++ : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer)
++ {
++ wxSizer* scrollSizer = new wxBoxSizer(wxVERTICAL);
++ wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this);
++@@ -217,15 +219,24 @@ EnvironmentSidebar::EnvironmentSidebar(ScenarioEditor& scenarioEditor, wxWindow*
++
++ wxSizer* waterSizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _T("Water settings"));
++ scrollSizer->Add(waterSizer, wxSizerFlags().Expand());
++- waterSizer->Add(new wxButton(scrolledWindow, ID_RecomputeWaterData, _("Reset Water Data")), wxSizerFlags().Expand());
++- waterSizer->Add(m_WaterTypeList = new VariableListBox(scrolledWindow, _("Water Type"), g_EnvironmentSettings.watertype), wxSizerFlags().Expand());
++- waterSizer->Add(new VariableSliderBox(scrolledWindow, _("Water height"), g_EnvironmentSettings.waterheight, 0.f, 1.2f), wxSizerFlags().Expand());
++- waterSizer->Add(new wxButton(scrolledWindow, ID_PickWaterHeight, _("Pick Water Height")), wxSizerFlags().Expand());
++- waterSizer->Add(new VariableSliderBox(scrolledWindow, _("Water waviness"), g_EnvironmentSettings.waterwaviness, 0.f, 10.f), wxSizerFlags().Expand());
++- waterSizer->Add(new VariableSliderBox(scrolledWindow, _("Water murkiness"), g_EnvironmentSettings.watermurkiness, 0.f, 1.f), wxSizerFlags().Expand());
++- waterSizer->Add(new VariableSliderBox(scrolledWindow, _("Wind angle"), g_EnvironmentSettings.windangle, -M_PIf, M_PIf), wxSizerFlags().Expand());
++- waterSizer->Add(new VariableColorBox(scrolledWindow, _("Water color"), g_EnvironmentSettings.watercolor), wxSizerFlags().Expand());
++- waterSizer->Add(new VariableColorBox(scrolledWindow, _("Water tint"), g_EnvironmentSettings.watertint), wxSizerFlags().Expand());
+++ waterSizer->Add(new wxButton(
+++ scrolledWindow, ID_RecomputeWaterData, _("Reset Water Data")), wxSizerFlags().Expand());
+++ waterSizer->Add(m_WaterTypeList = new VariableListBox(
+++ scrolledWindow, _("Water Type"), g_EnvironmentSettings.watertype), wxSizerFlags().Expand());
+++ waterSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Water height"), g_EnvironmentSettings.waterheight, 0.f, 1.2f), wxSizerFlags().Expand());
+++ waterSizer->Add(new wxButton(
+++ scrolledWindow, ID_PickWaterHeight, _("Pick Water Height")), wxSizerFlags().Expand());
+++ waterSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Water waviness"), g_EnvironmentSettings.waterwaviness, 0.f, 10.f), wxSizerFlags().Expand());
+++ waterSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Water murkiness"), g_EnvironmentSettings.watermurkiness, 0.f, 1.f), wxSizerFlags().Expand());
+++ waterSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Wind angle"), g_EnvironmentSettings.windangle, -static_cast<float>(M_PI), static_cast<float>(M_PI)), wxSizerFlags().Expand());
+++ waterSizer->Add(new VariableColorBox(
+++ scrolledWindow, _("Water color"), g_EnvironmentSettings.watercolor), wxSizerFlags().Expand());
+++ waterSizer->Add(new VariableColorBox(
+++ scrolledWindow, _("Water tint"), g_EnvironmentSettings.watertint), wxSizerFlags().Expand());
++
++ std::vector<std::wstring> list;
++ list.push_back(L"ocean"); list.push_back(L"lake"); list.push_back(L"clap");
++@@ -235,25 +246,40 @@ EnvironmentSidebar::EnvironmentSidebar(ScenarioEditor& scenarioEditor, wxWindow*
++ wxSizer* sunSizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _T("Sun / lighting settings"));
++ scrollSizer->Add(sunSizer, wxSizerFlags().Expand().Border(wxTOP, 8));
++
++- sunSizer->Add(new VariableSliderBox(scrolledWindow, _("Sun rotation"), g_EnvironmentSettings.sunrotation, -M_PIf, M_PIf), wxSizerFlags().Expand());
++- sunSizer->Add(new VariableSliderBox(scrolledWindow, _("Sun elevation"), g_EnvironmentSettings.sunelevation, -M_PIf/2, M_PIf/2), wxSizerFlags().Expand());
++- sunSizer->Add(new VariableSliderBox(scrolledWindow, _("Sun overbrightness"), g_EnvironmentSettings.sunoverbrightness, 1.0f, 3.0f), wxSizerFlags().Expand());
++- sunSizer->Add(new LightControl(scrolledWindow, wxSize(150, 150), g_EnvironmentSettings));
++- sunSizer->Add(new VariableColorBox(scrolledWindow, _("Sun color"), g_EnvironmentSettings.suncolor), wxSizerFlags().Expand());
++- sunSizer->Add(m_SkyList = new VariableListBox(scrolledWindow, _("Sky set"), g_EnvironmentSettings.skyset), wxSizerFlags().Expand());
++- sunSizer->Add(new VariableSliderBox(scrolledWindow, _("Fog Factor"), g_EnvironmentSettings.fogfactor, 0.0f, 0.01f), wxSizerFlags().Expand());
++- sunSizer->Add(new VariableSliderBox(scrolledWindow, _("Fog Thickness"), g_EnvironmentSettings.fogmax, 0.5f, 0.0f), wxSizerFlags().Expand());
++- sunSizer->Add(new VariableColorBox(scrolledWindow, _("Fog color"), g_EnvironmentSettings.fogcolor), wxSizerFlags().Expand());
++- sunSizer->Add(new VariableColorBox(scrolledWindow, _("Ambient color"), g_EnvironmentSettings.ambientcolor), wxSizerFlags().Expand());
+++ sunSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Sun rotation"), g_EnvironmentSettings.sunrotation, -static_cast<float>(M_PI), static_cast<float>(M_PI)), wxSizerFlags().Expand());
+++ sunSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Sun elevation"), g_EnvironmentSettings.sunelevation, -static_cast<float>(M_PI) / 2.0f, static_cast<float>(M_PI) / 2.0f), wxSizerFlags().Expand());
+++ sunSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Sun overbrightness"), g_EnvironmentSettings.sunoverbrightness, 1.0f, 3.0f), wxSizerFlags().Expand());
+++ sunSizer->Add(new LightControl(
+++ scrolledWindow, wxSize(150, 150), g_EnvironmentSettings));
+++ sunSizer->Add(new VariableColorBox(
+++ scrolledWindow, _("Sun color"), g_EnvironmentSettings.suncolor), wxSizerFlags().Expand());
+++ sunSizer->Add(m_SkyList = new VariableListBox(
+++ scrolledWindow, _("Sky set"), g_EnvironmentSettings.skyset), wxSizerFlags().Expand());
+++ sunSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Fog Factor"), g_EnvironmentSettings.fogfactor, 0.0f, 0.01f), wxSizerFlags().Expand());
+++ sunSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Fog Thickness"), g_EnvironmentSettings.fogmax, 0.5f, 0.0f), wxSizerFlags().Expand());
+++ sunSizer->Add(new VariableColorBox(
+++ scrolledWindow, _("Fog color"), g_EnvironmentSettings.fogcolor), wxSizerFlags().Expand());
+++ sunSizer->Add(new VariableColorBox(
+++ scrolledWindow, _("Ambient color"), g_EnvironmentSettings.ambientcolor), wxSizerFlags().Expand());
++
++ wxSizer* postProcSizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _T("Post-processing settings"));
++ scrollSizer->Add(postProcSizer, wxSizerFlags().Expand().Border(wxTOP, 8));
++
++- postProcSizer->Add(m_PostEffectList = new VariableListBox(scrolledWindow, _("Post Effect"), g_EnvironmentSettings.posteffect), wxSizerFlags().Expand());
++- postProcSizer->Add(new VariableSliderBox(scrolledWindow, _("Brightness"), g_EnvironmentSettings.brightness, -0.5f, 0.5f), wxSizerFlags().Expand());
++- postProcSizer->Add(new VariableSliderBox(scrolledWindow, _("Contrast (HDR)"), g_EnvironmentSettings.contrast, 0.5f, 1.5f), wxSizerFlags().Expand());
++- postProcSizer->Add(new VariableSliderBox(scrolledWindow, _("Saturation"), g_EnvironmentSettings.saturation, 0.0f, 2.0f), wxSizerFlags().Expand());
++- postProcSizer->Add(new VariableSliderBox(scrolledWindow, _("Bloom"), g_EnvironmentSettings.bloom, 0.2f, 0.0f), wxSizerFlags().Expand());
+++ postProcSizer->Add(m_PostEffectList = new VariableListBox(
+++ scrolledWindow, _("Post Effect"), g_EnvironmentSettings.posteffect), wxSizerFlags().Expand());
+++ postProcSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Brightness"), g_EnvironmentSettings.brightness, -0.5f, 0.5f), wxSizerFlags().Expand());
+++ postProcSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Contrast (HDR)"), g_EnvironmentSettings.contrast, 0.5f, 1.5f), wxSizerFlags().Expand());
+++ postProcSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Saturation"), g_EnvironmentSettings.saturation, 0.0f, 2.0f), wxSizerFlags().Expand());
+++ postProcSizer->Add(new VariableSliderBox(
+++ scrolledWindow, _("Bloom"), g_EnvironmentSettings.bloom, 0.2f, 0.0f), wxSizerFlags().Expand());
++
++ m_Conn = g_EnvironmentSettings.RegisterObserver(0, &SendToGame);
++ }
--- /dev/null
--- /dev/null
++From: s0600204 <s0600204>
++Date: Wed, 23 Feb 2022 21:30:38 +0100
++Subject: Fix building spidermonkey on systems with python 3.10
++
++Tested by:
++
++Langbart - macOS 10.15.7: homebrewed python 3.9.9 & 3.10.1
++andy5995 - Manjaro 21.2.3: python 3.10.2
++s0600204 - ArchLinux: python 3.10.2
++
++Bug: https://wildfiregames.com/forum/topic/65901-christmas-testing-bundle/#comment-472056
++Origin: upstream, commit:https://trac.wildfiregames.com/changeset/26475
++Origin: https://code.wildfiregames.com/D4437
++Bug-Debian: https://bugs.debian.org/1008075
++---
++ .../spidermonkey/FixPythonCollectionABC.diff | 87 ++++++++++++++++++++++
++ .../spidermonkey/FixVirtualenvForPython310.diff | 15 ++++
++ libraries/source/spidermonkey/patch.sh | 13 ++++
++ 3 files changed, 115 insertions(+)
++ create mode 100644 libraries/source/spidermonkey/FixPythonCollectionABC.diff
++ create mode 100644 libraries/source/spidermonkey/FixVirtualenvForPython310.diff
++
++diff --git a/libraries/source/spidermonkey/FixPythonCollectionABC.diff b/libraries/source/spidermonkey/FixPythonCollectionABC.diff
++new file mode 100644
++index 0000000..536a534
++--- /dev/null
+++++ b/libraries/source/spidermonkey/FixPythonCollectionABC.diff
++@@ -0,0 +1,87 @@
+++--- a/python/mach/mach/config.py
++++++ b/python/mach/mach/config.py
+++@@ -144,7 +144,7 @@
+++ return _
+++
+++
+++-class ConfigSettings(collections.Mapping):
++++class ConfigSettings(collections.abc.Mapping):
+++ """Interface for configuration settings.
+++
+++ This is the main interface to the configuration.
+++@@ -190,7 +190,7 @@
+++ will result in exceptions being raised.
+++ """
+++
+++- class ConfigSection(collections.MutableMapping, object):
++++ class ConfigSection(collections.abc.MutableMapping, object):
+++ """Represents an individual config section."""
+++ def __init__(self, config, name, settings):
+++ object.__setattr__(self, '_config', config)
+++--- a/python/mach/mach/decorators.py
++++++ b/python/mach/mach/decorators.py
+++@@ -159,7 +159,7 @@
+++ 'Conditions argument must take a list ' + \
+++ 'of functions. Found %s instead.'
+++
+++- if not isinstance(command.conditions, collections.Iterable):
++++ if not isinstance(command.conditions, collections.abc.Iterable):
+++ msg = msg % (command.name, type(command.conditions))
+++ raise MachError(msg)
+++
+++--- a/python/mach/mach/main.py
++++++ b/python/mach/mach/main.py
+++@@ -16,7 +16,7 @@
+++ import sys
+++ import traceback
+++ import uuid
+++-from collections import Iterable
++++from collections.abc import Iterable
+++
+++ from six import string_types
+++
+++--- a/python/mozbuild/mozbuild/backend/configenvironment.py
++++++ b/python/mozbuild/mozbuild/backend/configenvironment.py
+++@@ -9,7 +9,8 @@
+++ import sys
+++ import json
+++
+++-from collections import Iterable, OrderedDict
++++from collections import OrderedDict
++++from collections.abc import Iterable
+++ from types import ModuleType
+++
+++ import mozpack.path as mozpath
+++--- a/python/mozbuild/mozbuild/makeutil.py
++++++ b/python/mozbuild/mozbuild/makeutil.py
+++@@ -7,7 +7,7 @@
+++ import os
+++ import re
+++ import six
+++-from collections import Iterable
++++from collections.abc import Iterable
+++
+++
+++ class Makefile(object):
+++--- a/python/mozbuild/mozbuild/util.py
++++++ b/python/mozbuild/mozbuild/util.py
+++@@ -782,7 +782,7 @@
+++ self._strings = StrictOrderingOnAppendList()
+++ self._children = {}
+++
+++- class StringListAdaptor(collections.Sequence):
++++ class StringListAdaptor(collections.abc.Sequence):
+++ def __init__(self, hsl):
+++ self._hsl = hsl
+++
+++--- a/testing/mozbase/manifestparser/manifestparser/filters.py
++++++ b/testing/mozbase/manifestparser/manifestparser/filters.py
+++@@ -15,1 +15,2 @@
+++-from collections import defaultdict, MutableSequence
++++from collections import defaultdict
++++from collections.abc import MutableSequence
+++--- a/third_party/python/pipenv/pipenv/vendor/jinja2/sandbox.py
++++++ b/third_party/python/pipenv/pipenv/vendor/jinja2/sandbox.py
+++@@ -82,1 +82,1 @@
+++-from collections import MutableSet, MutableMapping, MutableSequence
++++from collections.abc import MutableSet, MutableMapping, MutableSequence
++diff --git a/libraries/source/spidermonkey/FixVirtualenvForPython310.diff b/libraries/source/spidermonkey/FixVirtualenvForPython310.diff
++new file mode 100644
++index 0000000..d023c63
++--- /dev/null
+++++ b/libraries/source/spidermonkey/FixVirtualenvForPython310.diff
++@@ -0,0 +1,15 @@
+++--- a/third_party/python/virtualenv/virtualenv.py
++++++ b/third_party/python/virtualenv/virtualenv.py
+++@@ -1804,7 +1804,11 @@
+++ pass
+++ else:
+++ # noinspection PyProtectedMember
+++- if sysconfig._get_default_scheme() == "posix_local":
++++ try: # Python >= 3.10
++++ default_scheme = sysconfig.get_default_scheme()
++++ except: # Python < 3.10
++++ default_scheme = sysconfig._get_default_scheme()
++++ if default_scheme == "posix_local":
+++ local_path = os.path.join(home_dir, "local")
+++ if not os.path.exists(local_path):
+++ os.mkdir(local_path)
++diff --git a/libraries/source/spidermonkey/patch.sh b/libraries/source/spidermonkey/patch.sh
++index 9059fd6..ca2b67c 100644
++--- a/libraries/source/spidermonkey/patch.sh
+++++ b/libraries/source/spidermonkey/patch.sh
++@@ -19,6 +19,14 @@ patch -p1 < ../FixSharedArray.diff
++ # (mentionned in the comments, no patch/commit found)
++ patch -p1 < ../FixPublicExport.diff
++
+++# In python 3.10 `sysconfig._get_default_scheme()` was renamed to
+++# `sysconfig.get_default_scheme()`. This breaks the version of
+++# `virtualenv` bundled with the spidermonkey source code.
+++#
+++# It is assumed that the updated version fetched for macOS systems
+++# above does not have this problem.
+++patch -p1 < ../FixVirtualenvForPython310.diff
+++
++ # Fix Rooted<void*> not working on VS17
++ # https://bugzilla.mozilla.org/show_bug.cgi?id=1679736
++ # (Landed in 85)
++@@ -34,6 +42,11 @@ patch -p1 < ../FixMSVCRootedVoid.diff
++ # so this patches it to an arbitrarily high Mac OS 11
++ patch -p1 < ../FixMacBuild.diff
++
+++# In python 3.3, the Collections' Abstract Base Classes were moved from `collections` to
+++# `collections.abc`, and aliases were set up for backwards compatibility.
+++# In python 3.10, these aliases were removed, requiring all code that used them to update.
+++patch -p1 < ../FixPythonCollectionABC.diff
+++
++ # Fix FP access breaking compilation on RPI3+
++ # https://bugzilla.mozilla.org/show_bug.cgi?id=1526653
++ # https://bugzilla.mozilla.org/show_bug.cgi?id=1536491
--- /dev/null
--- /dev/null
++TestStunClient
++allow-build-with-root.patch
++fix-bindir.patch
++Fix-build-mozjs-on-armhf.patch
++fix-build-mozjs-with-python-3.10.patch
++fix-build-atlas-gcc11-glibc-2.35.patch
--- /dev/null
--- /dev/null
++.so man6/0ad.6
--- /dev/null
--- /dev/null
++#!/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_strip:
++ dh_strip --dbgsym-migration='0ad-dbg (<< 0.0.20-2~)'
++
++override_dh_makeshlibs:
++ dh_makeshlibs -Xusr/lib/games/0ad
++
++override_dh_dwz:
++ dh_dwz -Xlibmozjs78-ps-release.so
--- /dev/null
--- /dev/null
++3.0 (quilt)
--- /dev/null
--- /dev/null
++# 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: insane-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 line length is 2981 characters (>512)
++0ad source: insane-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 line length is 1984 characters (>512)
++0ad source: insane-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 line length is 32005 characters (>512)
++0ad source: insane-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 line length is 32004 characters (>512)
++
++## 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
--- /dev/null
--- /dev/null
++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
--- /dev/null
--- /dev/null
++version=3
++https://releases.wildfiregames.com/0ad-([\d\.]+)-.*-unix-build\.tar\.gz