diff --git a/.github/workflows/test_js.yaml b/.github/workflows/test_js.yaml new file mode 100644 index 0000000..e605500 --- /dev/null +++ b/.github/workflows/test_js.yaml @@ -0,0 +1,34 @@ +name: JS Test multiselect + +on: + push: + paths: + - '**.js' + +jobs: + test: + name: TEST MULTISELECT + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install + run: | + npm --save-dev install \ + qunit \ + karma \ + karma-qunit \ + karma-coverage \ + karma-chrome-launcher \ + karma-module-resolver-preprocessor + npm --no-save install https://github.com/jquery/jquery#main + + - name: Run tests + run: | + node_modules/karma/bin/karma start js/karma.conf.js + + - name: Run coverage + run: | + npm run karma-test:coverage diff --git a/.github/workflows/test_py.yaml b/.github/workflows/test_py.yaml new file mode 100644 index 0000000..3c76870 --- /dev/null +++ b/.github/workflows/test_py.yaml @@ -0,0 +1,52 @@ +name: Test multiselect + +on: + push: + paths: + - '**.py' + +jobs: + test: + name: Test ${{ matrix.python }} - ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + + python: + - "2.7" + - "3.7" + - "3.8" + - "3.9" + - "3.10" + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: Install + run: | + pip install wheel + pip install coverage + pip install lxml + pip install zope.testrunner + pip install https://github.com/conestack/webresource/archive/master.zip + pip install https://github.com/conestack/yafowil/archive/master.zip + pip install -e .[test] + - name: Run tests + run: | + python --version + python -m yafowil.widget.multiselect.tests + - name: Run coverage + run: | + coverage run --source=src/yafowil/widget/multiselect --omit=src/yafowil/widget/multiselect/example.py -m yafowil.widget.multiselect.tests + coverage report --fail-under=99 \ No newline at end of file diff --git a/.gitignore b/.gitignore index f5f6b26..904259c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,12 @@ -*~ -*#* +*.egg-info *.pyc *.pyo -*.mo -*.egg-info -/develop-eggs/ -/parts/ -/bin/ -/eggs/ -/downloads/ -/var/ +/.coverage /dist/ -/.installed.cfg -/.pydevproject -/.project -/.DS_Store -/.mr.developer.cfg +/htmlcov/ +/js/karma/ +/node_modules/ +/package-lock.json +/py2/ +/py3/ +/pypy3/ diff --git a/CHANGES.rst b/CHANGES.rst index 0d8edb3..01146a4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,10 +1,15 @@ Changes ======= -1.4 (unreleased) +2.0 (unreleased) ---------------- -- No changes yet. +- Extend JS by ``multiselect_on_array_add`` and ``register_array_subscribers`` + functions to enable usage in ``yafowil.widget.array``. + [lenadax] + +- Rewrite JavaScript using ES6. + [lenadax] 1.3 (2018-07-16) diff --git a/LICENSE.rst b/LICENSE.rst index 01c1de3..f1304c3 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -2,7 +2,7 @@ License ======= Copyright (c) 2012-2021, BlueDynamics Alliance, Austria, Germany, Switzerland -Copyright (c) 2021, Yafowil Contributors +Copyright (c) 2021-2022, Yafowil Contributors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.rst b/README.rst index 150190e..43b2039 100644 --- a/README.rst +++ b/README.rst @@ -24,3 +24,5 @@ Contributors - Robert Niederreiter - Georg Bernhard + +- Lena Daxenbichler diff --git a/bootstrap.py b/bootstrap.py deleted file mode 100644 index f78186f..0000000 --- a/bootstrap.py +++ /dev/null @@ -1,170 +0,0 @@ -############################################################################## -# -# Copyright (c) 2006 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Bootstrap a buildout-based project - -Simply run this script in a directory containing a buildout.cfg. -The script accepts buildout command-line options, so you can -use the -c option to specify an alternate configuration file. -""" - -import os -import shutil -import sys -import tempfile - -from optparse import OptionParser - -tmpeggs = tempfile.mkdtemp() - -usage = '''\ -[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] - -Bootstraps a buildout-based project. - -Simply run this script in a directory containing a buildout.cfg, using the -Python that you want bin/buildout to use. - -Note that by using --find-links to point to local resources, you can keep -this script from going over the network. -''' - -parser = OptionParser(usage=usage) -parser.add_option("-v", "--version", help="use a specific zc.buildout version") - -parser.add_option("-t", "--accept-buildout-test-releases", - dest='accept_buildout_test_releases', - action="store_true", default=False, - help=("Normally, if you do not specify a --version, the " - "bootstrap script and buildout gets the newest " - "*final* versions of zc.buildout and its recipes and " - "extensions for you. If you use this flag, " - "bootstrap and buildout will get the newest releases " - "even if they are alphas or betas.")) -parser.add_option("-c", "--config-file", - help=("Specify the path to the buildout configuration " - "file to be used.")) -parser.add_option("-f", "--find-links", - help=("Specify a URL to search for buildout releases")) - - -options, args = parser.parse_args() - -###################################################################### -# load/install setuptools - -to_reload = False -try: - import pkg_resources - import setuptools -except ImportError: - ez = {} - - try: - from urllib.request import urlopen - except ImportError: - from urllib2 import urlopen - - # XXX use a more permanent ez_setup.py URL when available. - exec(urlopen('https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py' - ).read(), ez) - setup_args = dict(to_dir=tmpeggs, download_delay=0) - ez['use_setuptools'](**setup_args) - - if to_reload: - reload(pkg_resources) - import pkg_resources - # This does not (always?) update the default working set. We will - # do it. - for path in sys.path: - if path not in pkg_resources.working_set.entries: - pkg_resources.working_set.add_entry(path) - -###################################################################### -# Install buildout - -ws = pkg_resources.working_set - -cmd = [sys.executable, '-c', - 'from setuptools.command.easy_install import main; main()', - '-mZqNxd', tmpeggs] - -find_links = os.environ.get( - 'bootstrap-testing-find-links', - options.find_links or - ('http://downloads.buildout.org/' - if options.accept_buildout_test_releases else None) - ) -if find_links: - cmd.extend(['-f', find_links]) - -setuptools_path = ws.find( - pkg_resources.Requirement.parse('setuptools')).location - -requirement = 'zc.buildout' -version = options.version -if version is None and not options.accept_buildout_test_releases: - # Figure out the most recent final version of zc.buildout. - import setuptools.package_index - _final_parts = '*final-', '*final' - - def _final_version(parsed_version): - for part in parsed_version: - if (part[:1] == '*') and (part not in _final_parts): - return False - return True - index = setuptools.package_index.PackageIndex( - search_path=[setuptools_path]) - if find_links: - index.add_find_links((find_links,)) - req = pkg_resources.Requirement.parse(requirement) - if index.obtain(req) is not None: - best = [] - bestv = None - for dist in index[req.project_name]: - distv = dist.parsed_version - if _final_version(distv): - if bestv is None or distv > bestv: - best = [dist] - bestv = distv - elif distv == bestv: - best.append(dist) - if best: - best.sort() - version = best[-1].version -if version: - requirement = '=='.join((requirement, version)) -cmd.append(requirement) - -import subprocess -if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: - raise Exception( - "Failed to execute command:\n%s", - repr(cmd)[1:-1]) - -###################################################################### -# Import and run buildout - -ws.add_entry(tmpeggs) -ws.require(requirement) -import zc.buildout.buildout - -if not [a for a in args if '=' not in a]: - args.append('bootstrap') - -# if -c was provided, we push it back into args for buildout' main function -if options.config_file is not None: - args[0:0] = ['-c', options.config_file] - -zc.buildout.buildout.main(args) -shutil.rmtree(tmpeggs) diff --git a/buildout.cfg b/buildout.cfg deleted file mode 100644 index 8e26a69..0000000 --- a/buildout.cfg +++ /dev/null @@ -1,32 +0,0 @@ -[buildout] -parts = test coverage py -develop = . - -extensions = mr.developer -sources-dir = ${buildout:directory}/devsrc -always-checkout = force -auto-checkout = * - -[remotes] -cs = git://github.com/conestack -cs_push = git@github.com:conestack - -[sources] -yafowil = git ${remotes:cs}/yafowil.git pushurl=${remotes:cs_push}/yafowil.git - -[test] -recipe = zc.recipe.testrunner -eggs = - yafowil[test] - yafowil.widget.multiselect[test] -defaults = ['--auto-color', '--auto-progress'] - -[coverage] -recipe = zc.recipe.testrunner -eggs = ${test:eggs} -defaults = ['--coverage', '../../coverage', '-v', '--auto-progress'] - -[py] -recipe = zc.recipe.egg -eggs = ${test:eggs} -interpreter = py diff --git a/js/karma-coverage.js b/js/karma-coverage.js new file mode 100644 index 0000000..0072d7d --- /dev/null +++ b/js/karma-coverage.js @@ -0,0 +1,17 @@ +const percentage = { + lines: 100, + statements: 100, + functions: 100, + branches: 100 +} +var summary = require('./karma/coverage/coverage-summary.json'); + +for (let res in summary.total) { + if (summary.total[res].pct < percentage[res]) { + throw new Error( + `Coverage too low on ${res}, + expected: ${percentage[res]}, + got: ${summary.total[res].pct}` + ); + } +} \ No newline at end of file diff --git a/js/karma.conf.js b/js/karma.conf.js new file mode 100644 index 0000000..a12e076 --- /dev/null +++ b/js/karma.conf.js @@ -0,0 +1,70 @@ +// chromium binary +process.env.CHROME_BIN = '/usr/bin/chromium'; + +const RESOURCE_DIR = '../../src/yafowil/widget/multiselect/resources'; + +// karma config +module.exports = function(config) { + config.set({ + basePath: 'karma', + frameworks: [ + 'qunit' + ], + files: [{ + pattern: '../../node_modules/jquery/src/**/*.js', + type: 'module', + included: false + }, { + pattern: '../src/*.js', + type: 'module', + included: false + }, { + pattern: '../tests/test_*.js', + type: 'module' + }, { + pattern: `${RESOURCE_DIR}/multi-select/js/jquery.multi-select.js`, + type: 'module', + included: true + }], + browsers: [ + 'ChromeHeadless' + ], + autoWatch: false, + singleRun: true, + reporters: [ + 'progress', + 'coverage' + ], + coverageReporter: { + reporters: [ + { + type: 'json-summary', + dir: 'coverage/', + subdir: '.' + }, { + type: 'html', + dir: 'coverage/', + subdir: 'chrome-headless' + } + ] + }, + preprocessors: { + '../src/*.js': [ + 'coverage', + 'module-resolver' + ], + '../tests/*.js': [ + 'coverage', + 'module-resolver' + ] + }, + moduleResolverPreprocessor: { + addExtension: 'js', + customResolver: null, + ecmaVersion: 6, + aliases: { + jquery: '../../node_modules/jquery/src/jquery.js' + } + } + }); +}; \ No newline at end of file diff --git a/js/rollup.conf.js b/js/rollup.conf.js new file mode 100644 index 0000000..3e3c04a --- /dev/null +++ b/js/rollup.conf.js @@ -0,0 +1,47 @@ +import cleanup from 'rollup-plugin-cleanup'; +import {terser} from 'rollup-plugin-terser'; + +const out_dir = 'src/yafowil/widget/multiselect/resources'; + +const outro = ` +window.yafowil = window.yafowil || {}; +window.yafowil.multiselect = exports; +`; + +export default args => { + let conf = { + input: 'js/src/bundle.js', + plugins: [ + cleanup() + ], + output: [{ + name: 'yafowil_multiselect', + file: `${out_dir}/widget.js`, + format: 'iife', + outro: outro, + globals: { + jquery: 'jQuery' + }, + interop: 'default' + }], + external: [ + 'jquery' + ] + }; + if (args.configDebug !== true) { + conf.output.push({ + name: 'yafowil_multiselect', + file: `${out_dir}/widget.min.js`, + format: 'iife', + plugins: [ + terser() + ], + outro: outro, + globals: { + jquery: 'jQuery' + }, + interop: 'default' + }); + } + return conf; +}; diff --git a/js/src/bundle.js b/js/src/bundle.js new file mode 100644 index 0000000..ff3ab2a --- /dev/null +++ b/js/src/bundle.js @@ -0,0 +1,17 @@ +import $ from 'jquery'; + +import {MultiselectWidget} from './widget.js'; +import {register_array_subscribers} from './widget.js'; + +export * from './widget.js'; + +$(function() { + if (window.ts !== undefined) { + ts.ajax.register(MultiselectWidget.initialize, true); + } else if (window.bdajax !== undefined) { + bdajax.register(MultiselectWidget.initialize, true); + } else { + MultiselectWidget.initialize(); + } + register_array_subscribers(); +}); diff --git a/js/src/widget.js b/js/src/widget.js new file mode 100644 index 0000000..b4cd361 --- /dev/null +++ b/js/src/widget.js @@ -0,0 +1,35 @@ +import $ from 'jquery'; + +export class MultiselectWidget { + static initialize(context) { + $('select.multiselect', context).each(function() { + if (window.yafowil_array !== undefined && + window.yafowil_array.inside_template($(this))) { + return; + } + new MultiselectWidget($(this)); + }); + } + + constructor(elem) { + elem.data('yafowil-multiselect', this); + this.elem = elem; + this.elem.multiSelect(); + } +} + + +////////////////////////////////////////////////////////////////////////////// +// yafowil.widget.array integration +////////////////////////////////////////////////////////////////////////////// + +export function multiselect_on_array_add(inst, context) { + MultiselectWidget.initialize(context); +} + +export function register_array_subscribers() { + if (window.yafowil_array === undefined) { + return; + } + window.yafowil_array.on_array_event('on_add', multiselect_on_array_add); +} diff --git a/js/tests/test_widget.js b/js/tests/test_widget.js new file mode 100644 index 0000000..f8ab667 --- /dev/null +++ b/js/tests/test_widget.js @@ -0,0 +1,55 @@ +import {MultiselectWidget} from '../src/widget.js'; +import {register_array_subscribers} from "../src/widget"; + +QUnit.test('initialize', assert => { + let el = $('').addClass('multiselect').appendTo('body'); + MultiselectWidget.initialize(); + let wid = el.data('yafowil-multiselect'); + assert.ok(wid); +}); + +QUnit.test('register_array_subscribers', assert => { + let _array_subscribers = { + on_add: [] + }; + + // window.yafowil_array is undefined - return + register_array_subscribers(); + assert.deepEqual(_array_subscribers['on_add'], []); + + // patch yafowil_array + window.yafowil_array = { + on_array_event: function(evt_name, evt_function) { + _array_subscribers[evt_name] = evt_function; + }, + inside_template(elem) { + return elem.parents('.arraytemplate').length > 0; + } + }; + register_array_subscribers(); + + // create table DOM + let table = $('