First commit
authorThierry Florac <thierry.florac@onf.fr>
Thu, 19 Feb 2015 10:55:46 +0100
changeset 0 7a0b409fd4b8
child 1 dfa0508e4055
First commit
.hgignore
.installed.cfg
LICENSE
MANIFEST.in
bootstrap.py
buildout.cfg
docs/HISTORY.txt
docs/README.txt
setup.py
src/pyams_form.egg-info/PKG-INFO
src/pyams_form.egg-info/SOURCES.txt
src/pyams_form.egg-info/dependency_links.txt
src/pyams_form.egg-info/entry_points.txt
src/pyams_form.egg-info/namespace_packages.txt
src/pyams_form.egg-info/not-zip-safe
src/pyams_form.egg-info/requires.txt
src/pyams_form.egg-info/top_level.txt
src/pyams_form/__init__.py
src/pyams_form/configure.zcml
src/pyams_form/doctests/README.txt
src/pyams_form/form.py
src/pyams_form/group.py
src/pyams_form/include.py
src/pyams_form/interfaces/__init__.py
src/pyams_form/interfaces/form.py
src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.mo
src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po
src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po~
src/pyams_form/locales/pyams_form.pot
src/pyams_form/schema.py
src/pyams_form/search.py
src/pyams_form/templates/form.pt
src/pyams_form/templates/inner-form.pt
src/pyams_form/templates/search.pt
src/pyams_form/templates/widget-form.pt
src/pyams_form/terms.py
src/pyams_form/tests/__init__.py
src/pyams_form/tests/test_utilsdocs.py
src/pyams_form/tests/test_utilsdocstrings.py
src/pyams_form/viewlet.py
src/pyams_form/widget/__init__.py
src/pyams_form/widget/configure.zcml
src/pyams_form/widget/templates/button-display.pt
src/pyams_form/widget/templates/button-input.pt
src/pyams_form/widget/templates/close-display.pt
src/pyams_form/widget/templates/close-input.pt
src/pyams_form/widget/templates/radio-display.pt
src/pyams_form/widget/templates/radio-input.pt
src/pyams_form/widget/templates/reset-display.pt
src/pyams_form/widget/templates/reset-input.pt
src/pyams_form/widget/templates/select-input.pt
src/pyams_form/widget/templates/submit-display.pt
src/pyams_form/widget/templates/submit-input.pt
src/pyams_form/widget/templates/text-display.pt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,19 @@
+
+syntax: regexp
+^develop-eggs$
+syntax: regexp
+^parts$
+syntax: regexp
+^bin$
+syntax: regexp
+^\.installed\.cfg$
+syntax: regexp
+^\.settings$
+syntax: regexp
+^build$
+syntax: regexp
+^dist$
+syntax: regexp
+^\.idea$
+syntax: regexp
+.*\.pyc$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.installed.cfg	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,73 @@
+[buildout]
+installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-utils.egg-link
+parts = package i18n pyflakes test
+
+[package]
+__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pcreate
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/ptweens
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/prequest
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pdistreport
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pserve
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pshell
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/proutes
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pviews
+__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zc.buildout-2.3.1-py3.4.egg
+_b = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
+_d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
+_e = /var/local/env/pyams/eggs
+bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
+develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
+eggs = pyams_form
+	pyams_skin
+	pyramid
+	z3c.form
+	zope.component
+	zope.interface
+eggs-directory = /var/local/env/pyams/eggs
+recipe = zc.recipe.egg
+
+[i18n]
+__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pybabel
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pot-create
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/polint
+__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zc.buildout-2.3.1-py3.4.egg
+_b = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
+_d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
+_e = /var/local/env/pyams/eggs
+bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
+develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
+eggs = babel
+	lingua
+eggs-directory = /var/local/env/pyams/eggs
+recipe = zc.recipe.egg
+
+[pyflakes]
+__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pyflakes
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pyflakes
+__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zc.buildout-2.3.1-py3.4.egg
+_b = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
+_d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
+_e = /var/local/env/pyams/eggs
+bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
+develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
+eggs = pyflakes
+eggs-directory = /var/local/env/pyams/eggs
+entry-points = pyflakes=pyflakes.scripts.pyflakes:main
+initialization = if not sys.argv[1:]: sys.argv[1:] = ["src"]
+recipe = zc.recipe.egg
+scripts = pyflakes
+
+[test]
+__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/parts/test
+	/home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/test
+__buildout_signature__ = zc.recipe.testrunner-2.0.0-py3.4.egg zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zope.testrunner-4.4.6-py3.4.egg zc.buildout-2.3.1-py3.4.egg zope.interface-4.1.2-py3.4-linux-x86_64.egg zope.exceptions-4.0.7-py3.4.egg six-1482e89f68d85eea27f4ed7749df2819
+_b = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
+_d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
+_e = /var/local/env/pyams/eggs
+bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin
+develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs
+eggs = pyams_form [test]
+eggs-directory = /var/local/env/pyams/eggs
+location = /home/tflorac/Dropbox/src/PyAMS/pyams_form/parts/test
+recipe = zc.recipe.testrunner
+script = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,42 @@
+Zope Public License (ZPL) Version 2.1
+=====================================
+
+A copyright notice accompanies this license document that identifies
+the copyright holders.
+
+This license has been certified as open source. It has also been designated
+as GPL compatible by the Free Software Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions in source code must retain the accompanying copyright
+      notice, this list of conditions, and the following disclaimer.
+   2. Redistributions in binary form must reproduce the accompanying copyright
+      notice, this list of conditions, and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+   3. Names of the copyright holders must not be used to endorse or promote
+      products derived from this software without prior written permission
+      from the copyright holders.
+   4. The right to distribute this software or to use it for any purpose does
+      not give you the right to use Servicemarks (sm) or Trademarks (tm) of the
+      copyright holders. Use of them is covered by separate agreement with the
+      copyright holders.
+   5. If any files are modified, you must cause the modified files to carry
+      prominent notices stating that you changed the files and the date of any
+      change.
+
+
+Disclaimer
+==========
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED
+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 COPYRIGHT HOLDERS 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	Thu Jan 01 00:00:00 1970 +0000
+++ b/MANIFEST.in	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,5 @@
+include *.txt
+recursive-include docs *
+recursive-include src *
+global-exclude *.pyc
+global-exclude *.*~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bootstrap.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,178 @@
+##############################################################################
+#
+# 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"))
+parser.add_option("--allow-site-packages",
+                  action="store_true", default=False,
+                  help=("Let bootstrap.py use existing site packages"))
+
+
+options, args = parser.parse_args()
+
+######################################################################
+# load/install setuptools
+
+try:
+    if options.allow_site_packages:
+        import setuptools
+        import pkg_resources
+    from urllib.request import urlopen
+except ImportError:
+    from urllib2 import urlopen
+
+ez = {}
+exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez)
+
+if not options.allow_site_packages:
+    # ez_setup imports site, which adds site packages
+    # this will remove them from the path to ensure that incompatible versions 
+    # of setuptools are not in the path
+    import site
+    # inside a virtualenv, there is no 'getsitepackages'. 
+    # We can't remove these reliably
+    if hasattr(site, 'getsitepackages'):
+        for sitepackage_path in site.getsitepackages():
+            sys.path[:] = [x for x in sys.path if sitepackage_path not in x]
+
+setup_args = dict(to_dir=tmpeggs, download_delay=0)
+ez['use_setuptools'](**setup_args)
+import setuptools
+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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/buildout.cfg	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,63 @@
+[buildout]
+eggs-directory = /var/local/env/pyams/eggs
+
+socket-timeout = 3
+show-picked-versions = true
+newest = false
+
+allow-hosts =
+    bitbucket.org
+    *.python.org
+    *.sourceforge.net
+    github.com
+
+#extends = http://download.ztfy.org/webapp/ztfy.webapp.dev.cfg
+versions = versions
+newest = false
+#allow-picked-versions = false
+
+src = src
+develop = .
+          ../pyams_skin
+          ../pyams_utils
+
+parts =
+    package
+    i18n
+    pyflakes
+    test
+
+[package]
+recipe = zc.recipe.egg
+eggs =
+    pyams_form
+    pyams_skin
+    pyramid
+    z3c.form
+    zope.component
+    zope.interface
+
+[i18n]
+recipe = zc.recipe.egg
+eggs =
+    babel
+    lingua
+
+[pyflakes]
+recipe = zc.recipe.egg
+eggs = pyflakes
+scripts = pyflakes
+entry-points = pyflakes=pyflakes.scripts.pyflakes:main
+initialization = if not sys.argv[1:]: sys.argv[1:] = ["${buildout:src}"]
+
+[pyflakesrun]
+recipe = collective.recipe.cmd
+on_install = true
+cmds = ${buildout:develop}/bin/${pyflakes:scripts}
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = pyams_form [test]
+
+[versions]
+pyams_base = 0.1.0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+"""
+This module contains pyams_ package
+"""
+import os
+from setuptools import setup, find_packages
+
+DOCS = os.path.join(os.path.dirname(__file__),
+                    'docs')
+
+README = os.path.join(DOCS, 'README.txt')
+HISTORY = os.path.join(DOCS, 'HISTORY.txt')
+
+version = '0.1.0'
+long_description = open(README).read() + '\n\n' + open(HISTORY).read()
+
+tests_require = []
+
+setup(name='pyams_form',
+      version=version,
+      description="PyAMS base form interfaces and classes",
+      long_description=long_description,
+      classifiers=[
+          "License :: OSI Approved :: Zope Public License",
+          "Development Status :: 4 - Beta",
+          "Programming Language :: Python",
+          "Framework :: Pyramid",
+          "Topic :: Software Development :: Libraries :: Python Modules",
+      ],
+      keywords='Pyramid PyAMS form',
+      author='Thierry Florac',
+      author_email='tflorac@ulthar.net',
+      url='http://hg.ztfy.org/pyams/pyams_form',
+      license='ZPL',
+      packages=find_packages('src'),
+      package_dir={'': 'src'},
+      namespace_packages=[],
+      include_package_data=True,
+      package_data={'': ['*.zcml', '*.txt', '*.pt', '*.pot', '*.po', '*.mo', '*.png', '*.gif', '*.jpeg', '*.jpg', '*.css', '*.js']},
+      zip_safe=False,
+      # uncomment this to be able to run tests with setup.py
+      test_suite="pyams_form.tests.test_utilsdocs.test_suite",
+      tests_require=tests_require,
+      extras_require=dict(test=tests_require),
+      install_requires=[
+          'setuptools',
+          # -*- Extra requirements: -*-
+          'pyams_skin',
+          'pyramid',
+          'pyramid_zope_request',
+          'z3c.form',
+          'zope.component',
+          'zope.interface',
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      """,
+      )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form.egg-info/PKG-INFO	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,18 @@
+Metadata-Version: 1.1
+Name: pyams-form
+Version: 0.1.0
+Summary: PyAMS base form interfaces and classes
+Home-page: http://hg.ztfy.org/pyams/pyams_form
+Author: Thierry Florac
+Author-email: tflorac@ulthar.net
+License: ZPL
+Description: 
+        
+        
+Keywords: Pyramid PyAMS form
+Platform: UNKNOWN
+Classifier: License :: OSI Approved :: Zope Public License
+Classifier: Development Status :: 4 - Beta
+Classifier: Programming Language :: Python
+Classifier: Framework :: Pyramid
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form.egg-info/SOURCES.txt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,44 @@
+MANIFEST.in
+setup.py
+docs/HISTORY.txt
+docs/README.txt
+src/pyams_form/__init__.py
+src/pyams_form/configure.zcml
+src/pyams_form/form.py
+src/pyams_form/group.py
+src/pyams_form/schema.py
+src/pyams_form/terms.py
+src/pyams_form/viewlet.py
+src/pyams_form.egg-info/PKG-INFO
+src/pyams_form.egg-info/SOURCES.txt
+src/pyams_form.egg-info/dependency_links.txt
+src/pyams_form.egg-info/entry_points.txt
+src/pyams_form.egg-info/namespace_packages.txt
+src/pyams_form.egg-info/not-zip-safe
+src/pyams_form.egg-info/requires.txt
+src/pyams_form.egg-info/top_level.txt
+src/pyams_form/doctests/README.txt
+src/pyams_form/interfaces/__init__.py
+src/pyams_form/interfaces/form.py
+src/pyams_form/locales/pyams_form.pot
+src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.mo
+src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po
+src/pyams_form/templates/form.pt
+src/pyams_form/templates/inner-form.pt
+src/pyams_form/templates/widget-form.pt
+src/pyams_form/tests/__init__.py
+src/pyams_form/tests/test_utilsdocs.py
+src/pyams_form/tests/test_utilsdocstrings.py
+src/pyams_form/widget/__init__.py
+src/pyams_form/widget/configure.zcml
+src/pyams_form/widget/templates/button-display.pt
+src/pyams_form/widget/templates/button-input.pt
+src/pyams_form/widget/templates/close-display.pt
+src/pyams_form/widget/templates/close-input.pt
+src/pyams_form/widget/templates/radio-input.pt
+src/pyams_form/widget/templates/reset-display.pt
+src/pyams_form/widget/templates/reset-input.pt
+src/pyams_form/widget/templates/select-input.pt
+src/pyams_form/widget/templates/submit-display.pt
+src/pyams_form/widget/templates/submit-input.pt
+src/pyams_form/widget/templates/text-display.pt
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form.egg-info/dependency_links.txt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form.egg-info/entry_points.txt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,3 @@
+
+      # -*- Entry points: -*-
+      
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form.egg-info/namespace_packages.txt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form.egg-info/not-zip-safe	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form.egg-info/requires.txt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,9 @@
+setuptools
+pyams_skin
+pyramid
+pyramid_zope_request
+z3c.form
+zope.component
+zope.interface
+
+[test]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form.egg-info/top_level.txt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,1 @@
+pyams_form
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/__init__.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
+
+from pyramid.i18n import TranslationStringFactory
+_ = TranslationStringFactory('pyams_form')
+
+
+def includeme(config):
+    """Pyramid include
+
+    Split in another package to remove cyclic dependencies with TranslationStringFactory
+    """
+    from .include import include_package
+    include_package(config)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/configure.zcml	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,19 @@
+<configure
+	xmlns="http://pylonshq.com/pyramid"
+	xmlns:i18n="http://namespaces.zope.org/i18n"
+	i18n_domain="pyams_form">
+
+	<include package="pyramid_zcml" />
+
+	<include package="zope.i18n" file="meta.zcml" />
+	<include package="z3c.form" file="meta.zcml" />
+
+	<include package="pyams_template" file="meta.zcml" />
+	<include package="pyams_viewlet" file="meta.zcml" />
+
+	<i18n:registerTranslations directory="locales" />
+
+
+	<include package=".widget" />
+
+</configure>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/doctests/README.txt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,3 @@
+==================
+pyams_ package
+==================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/form.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,411 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import json
+
+# import interfaces
+from pyams_form.interfaces.form import IFormLayer, IForm, IAJAXForm, IInnerSubForm, IInnerTabForm, \
+    ICustomUpdateSubForm, IFormCreatedEvent, FormCreatedEvent
+from pyams_form.interfaces.form import IAddFormButtons, IModalAddFormButtons, IEditFormButtons, \
+    IModalEditFormButtons, IModalDisplayFormButtons
+from pyams_form.interfaces.form import FormObjectCreatedEvent, FormObjectModifiedEvent
+from pyams_skin.interfaces import IDialog, ISkinnable
+from pyams_template.interfaces import IContentTemplate, ILayoutTemplate
+from pyramid_chameleon.interfaces import IChameleonTranslate
+from z3c.form.interfaces import DISPLAY_MODE, IErrorViewSnippet
+from zope.publisher.interfaces.browser import IBrowserRequest
+
+# import packages
+from pyams_form.group import GroupsBasedForm
+from pyams_skin.skin import apply_skin
+from pyramid.decorator import reify
+from pyramid.events import subscriber
+from pyramid.response import Response
+from pyramid.url import resource_url
+from pyramid_zope_request import PyramidToPublisher, PyramidPublisherRequest
+from z3c.form.button import Buttons
+from z3c.form.form import applyChanges, \
+    Form, AddForm as BaseAddForm, EditForm as BaseEditForm, DisplayForm as BaseDisplayForm
+from zope.component import queryUtility
+from zope.interface import implementer, alsoProvides
+from zope.lifecycleevent import Attributes, ObjectCreatedEvent
+from zope.schema.fieldproperty import FieldProperty
+
+from pyams_form import _
+
+
+def get_form_weight(form):
+    """Try to get form weight attribute"""
+    try:
+        return form.weight
+    except AttributeError:
+        return 0
+
+
+REDIRECT_STATUS_CODES = (300, 301, 302, 303, 304, 305, 307)
+
+
+@PyramidToPublisher(IBrowserRequest)
+@implementer(IForm, ISkinnable)
+class BaseForm(GroupsBasedForm, Form):
+    """Base form class"""
+
+    layout = None
+    layer = IFormLayer
+
+    edit_permission = FieldProperty(IForm['edit_permission'])
+
+    title = FieldProperty(IForm['title'])
+    legend = FieldProperty(IForm['legend'])
+    css_class = FieldProperty(IForm['css_class'])
+    icon_css_class = FieldProperty(IForm['icon_css_class'])
+    autocomplete = FieldProperty(IForm['autocomplete'])
+    _warn_on_change = FieldProperty(IForm['warn_on_change'])
+
+    label_css_class = FieldProperty(IForm['label_css_class'])
+    input_css_class = FieldProperty(IForm['input_css_class'])
+
+    display_hints_on_widgets = FieldProperty(IForm['display_hints_on_widgets'])
+    handle_upload = FieldProperty(IForm['handle_upload'])
+    callbacks = FieldProperty(IForm['callbacks'])
+
+    subforms_legend = FieldProperty(IForm['subforms_legend'])
+
+    def __init__(self, context, request):
+        GroupsBasedForm.__init__(self)
+        Form.__init__(self, context, request)
+        for req in (request, request._request):
+            alsoProvides(req, self.layer)
+        request.registry.notify(FormCreatedEvent(self))
+
+    def update(self):
+        if self.edit_permission and not self.request.has_permission(self.edit_permission, self.getContent()):
+            self.mode = DISPLAY_MODE
+        Form.update(self)
+        [subform.update() for subform in self.subforms]
+        [tabform.update() for tabform in self.tabforms]
+
+    def get_form_action(self):
+        return self.action
+
+    @reify
+    def subforms(self):
+        registry = self.request.registry
+        return sorted((adapter[1] for adapter in registry.getAdapters((self,), IInnerSubForm)),
+                      key=get_form_weight)
+
+    @reify
+    def tabforms(self):
+        registry = self.request.registry
+        return sorted((adapter[1] for adapter in registry.getAdapters((self,), IInnerTabForm)),
+                      key=get_form_weight)
+
+    @property
+    def forms(self):
+        return [self, ] + self.subforms + self.tabforms
+
+    @reify
+    def warn_on_change(self):
+        if self._warn_on_change is True:
+            return 'true'
+        elif self._warn_on_change is False:
+            return 'false'
+        else:
+            return None
+
+    @property
+    def is_dialog(self):
+        return IDialog.providedBy(self)
+
+    def get_widget_callback(self, widget):
+        return (self.callbacks or {}).get(widget)
+
+    # Default z3c.form methods
+
+    @property
+    def errors(self):
+        result = []
+        for form in self.forms:
+            result.extend(form.widgets.errors)
+        return result
+
+    def update_content(self, content, data):
+        changes = applyChanges(self, content, data)
+        for subform in self.subforms + self.tabforms:
+            if ICustomUpdateSubForm.providedBy(subform):
+                updates = ICustomUpdateSubForm(subform).update_content(content, data)
+                if isinstance(updates, dict):
+                    changes.update(updates)
+            else:
+                changes.update(applyChanges(subform, content, data))
+        return changes
+
+    def render(self):
+        request = self.request
+        if isinstance(request, PyramidPublisherRequest):
+            request = request._request
+        cdict = {'context': self.context,
+                 'request': request,
+                 'view': self,
+                 'translate': queryUtility(IChameleonTranslate)}
+        if self.template is None:
+            registry = request.registry
+            template = registry.queryMultiAdapter((self, request, self.context), IContentTemplate)
+            if template is None:
+                template = registry.getMultiAdapter((self, request), IContentTemplate)
+            return template(**cdict)
+        return self.template(**cdict)
+
+    def __call__(self, **kwargs):
+        self.update()
+        if self.request.response.status_code in REDIRECT_STATUS_CODES:
+            return Response('')
+
+        request = self.request
+        if isinstance(request, PyramidPublisherRequest):
+            request = request._request
+        cdict = {'context': self.context,
+                 'request': request,
+                 'view': self,
+                 'translate': queryUtility(IChameleonTranslate)}
+        cdict.update(kwargs)
+        if self.layout is None:
+            registry = request.registry
+            layout = registry.queryMultiAdapter((self, request, self.context),
+                                                ILayoutTemplate)
+            if layout is None:
+                layout = registry.getMultiAdapter((self, request), ILayoutTemplate)
+            return Response(layout(**cdict))
+        return Response(self.layout(**cdict))
+
+    def get_skin(self, request=None):
+        return request.annotations.get('__skin__')
+
+
+@implementer(IAJAXForm)
+class AJAXForm(BaseForm):
+    """AJAX form base class"""
+
+    ajax_handler = FieldProperty(IAJAXForm['ajax_handler'])
+    form_options = FieldProperty(IAJAXForm['form_options'])
+    form_target = FieldProperty(IAJAXForm['form_target'])
+    ajax_callback = FieldProperty(IAJAXForm['ajax_callback'])
+
+    def get_form_action(self):
+        return resource_url(self.context, self.request, self.request.view_name)
+
+    def get_form_options(self):
+        return json.dumps(self.form_options) if self.form_options else None
+
+    def get_ajax_handler(self):
+        return resource_url(self.context, self.request, self.ajax_handler)
+
+    def get_ajax_errors(self):
+        """Extract form errors in AJAX format"""
+        translate = self.request.localizer.translate
+        errors = {'status': u'error',
+                  'error_message': translate(self.status)}
+        registry = self.request.registry
+        for error in self.errors:
+            if isinstance(error, Exception):
+                error = registry.getMultiAdapter((error, self.request, None, None, self, self.request),
+                                                 IErrorViewSnippet)
+
+            error.update()
+            if hasattr(error, 'widget'):
+                widget = error.widget
+                if widget is not None:
+                    errors.setdefault('widgets', []).append({'name': widget.name,
+                                                             'label': translate(widget.label),
+                                                             'message': translate(error.message)})
+                else:
+                    errors.setdefault('messages', []).append({'message': translate(error.message)})
+            else:
+                errors.setdefault('messages', []).append(translate(error.message))
+        return errors
+
+    def get_ajax_output(self, changes):
+        """Extract AJAX POST output"""
+        raise NotImplementedError
+
+
+#
+# Add forms
+#
+
+class AddForm(AJAXForm, BaseAddForm):
+    """Add form base class"""
+
+    buttons = Buttons(IAddFormButtons)
+
+    legend = _("Add form")
+    formErrorsMessage = _("There were some errors.")
+
+    def updateActions(self):
+        BaseAddForm.updateActions(self)
+        if 'add' in self.actions:
+            self.actions['add'].addClass('btn-primary')
+
+    def createAndAdd(self, data):
+        registry = self.request.registry
+        object = self.create(data)
+        registry.notify(ObjectCreatedEvent(object))
+        self.update_content(object, data)
+        self.add(object)
+        registry.notify(FormObjectCreatedEvent(object, self))
+        return object
+
+
+class AJAXAddForm(AddForm):
+    """AJAX add form"""
+
+    def __call__(self):
+        self.updateWidgets()
+        data, errors = self.extractData()
+        if errors or self.errors:
+            return self.get_ajax_errors()
+        result = self.createAndAdd(data)
+        return self.get_ajax_output(result)
+
+    def get_ajax_output(self, changes):
+        return {'status': 'reload',
+                'location': self.nextURL()}
+
+
+@implementer(IDialog)
+class DialogAddForm(AddForm):
+    """Modal dialog add form"""
+
+    buttons = Buttons(IModalAddFormButtons)
+    dialog_class = 'modal-medium'
+
+
+#
+# Edit forms
+#
+
+class EditForm(AJAXForm, BaseEditForm):
+    """Edit form base class"""
+
+    buttons = Buttons(IEditFormButtons)
+
+    legend = _("Edit form")
+    formErrorsMessage = _("There were some errors.")
+    successMessage = _("Data successfully updated.")
+    noChangesMessage = _("No changes were applied.")
+
+    def updateActions(self):
+        BaseEditForm.updateActions(self)
+        if 'submit' in self.actions:
+            self.actions['submit'].addClass('btn-primary')
+
+    def applyChanges(self, data):
+        content = self.getContent()
+        changes = self.update_content(content, data)
+        if changes:
+            descriptions = []
+            for interface, names in changes.items():
+                descriptions.append(Attributes(interface, *names))
+            self.request.registry.notify(FormObjectModifiedEvent(content, self, *descriptions))
+        return changes
+
+
+class AJAXEditForm(EditForm):
+    """AJAX edit form"""
+
+    def __call__(self):
+        self.updateWidgets()
+        data, errors = self.extractData()
+        if errors or self.errors:
+            return self.get_ajax_errors()
+        changes = self.applyChanges(data)
+        return self.get_ajax_output(changes)
+
+    def get_ajax_output(self, changes):
+        if changes:
+            status = 'success'
+            message = self.successMessage
+        else:
+            status = 'info'
+            message = self.noChangesMessage
+        translate = self.request.localizer.translate
+        return {'status': status,
+                'message': translate(message)}
+
+
+@implementer(IDialog)
+class DialogEditForm(EditForm):
+    """Modal dialog edit form"""
+
+    buttons = Buttons(IModalEditFormButtons)
+    dialog_class = 'modal-medium'
+
+
+#
+# Display forms
+#
+
+class DisplayForm(BaseForm, BaseDisplayForm):
+    """Display form base class"""
+
+
+@implementer(IDialog)
+class DialogDisplayForm(DisplayForm):
+    """Modal dialog display form"""
+
+    buttons = Buttons(IModalDisplayFormButtons)
+    dialog_class = 'modal-medium'
+
+
+#
+# Form events subscribers
+#
+
+@subscriber(IFormCreatedEvent, context_selector=ISkinnable)
+def handle_form_skin(event):
+    request = event.object.request
+    if isinstance(request, PyramidPublisherRequest):
+        request = request._request
+    skin = ISkinnable(event.object).get_skin(request)
+    if skin is not None:
+        apply_skin(request, skin)
+
+
+class FormSelector(object):
+    """Form event selector
+
+    This selector can be used by subscriber to filter form events
+    """
+
+    def __init__(self, ifaces, config):
+        if not isinstance(ifaces, (list, tuple)):
+            ifaces = (ifaces,)
+        self.interfaces = ifaces
+
+    def text(self):
+        return 'form_selector = %s' % str(self.interfaces)
+
+    phash = text
+
+    def __call__(self, event):
+        for intf in self.interfaces:
+            try:
+                if intf.providedBy(event.form):
+                    return True
+            except (AttributeError, TypeError):
+                if isinstance(event.form, intf):
+                    return True
+        return False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/group.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,147 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_form.interfaces.form import IFormWidgetsGroup, IGroupsBasedForm
+
+# import packages
+from pyramid.decorator import reify
+from zope.interface import implementer
+from zope.schema.fieldproperty import FieldProperty
+
+
+@implementer(IFormWidgetsGroup)
+class FormWidgetsGroup(object):
+    """Form widgets group"""
+
+    id = FieldProperty(IFormWidgetsGroup['id'])
+    widgets = FieldProperty(IFormWidgetsGroup['widgets'])
+    legend = FieldProperty(IFormWidgetsGroup['legend'])
+    help = FieldProperty(IFormWidgetsGroup['help'])
+    _css_class = FieldProperty(IFormWidgetsGroup['css_class'])
+    switch = FieldProperty(IFormWidgetsGroup['switch'])
+    checkbox_switch = FieldProperty(IFormWidgetsGroup['checkbox_switch'])
+    checkbox_field = FieldProperty(IFormWidgetsGroup['checkbox_field'])
+    checkbox_widget = FieldProperty(IFormWidgetsGroup['checkbox_widget'])
+    hide_if_empty = FieldProperty(IFormWidgetsGroup['hide_if_empty'])
+
+    def __init__(self, id, widgets=None, legend=None, help=None, css_class='', switch=False,
+                 checkbox_switch=False, checkbox_field=None, hide_if_empty=False):
+        assert (not checkbox_switch) or checkbox_field, "You must define checkbox field when using checkbox switch"
+        self.id = id
+        self.widgets = widgets or []
+        self.legend = id if legend is None else legend
+        self.help = help
+        self._css_class = css_class
+        self.switch = switch
+        self.checkbox_switch = checkbox_switch
+        self.checkbox_field = checkbox_field
+        self.hide_if_empty = hide_if_empty
+
+    @property
+    def css_class(self):
+        css_class = self._css_class
+        if self.switch:
+            if self.checkbox_switch:
+                css_class += ' checker'
+            else:
+                css_class += ' switcher'
+        return css_class
+
+    @property
+    def checkbox_widget(self):
+        if self.checkbox_field is None:
+            return None
+        for widget in self.widgets:
+            if widget.field is self.checkbox_field.field:
+                return widget
+
+    @reify
+    def visible(self):
+        if self.checkbox_switch:
+            widget = self.checkbox_widget
+            context = widget.context
+            name = widget.field.getName()
+            value = getattr(context, name, None)
+            return bool(value)
+        else:
+            if not (self.switch and self.hide_if_empty):
+                return True
+            for widget in self.widgets:
+                if not widget.ignoreContext:
+                    field = widget.field
+                    context = widget.context
+                    name = field.getName()
+                    value = getattr(context, name, None)
+                    if value and (value != field.default):
+                        return True
+            return False
+
+    @property
+    def visible_widgets(self):
+        for widget in self.widgets:
+            if (self.checkbox_field is None) or (widget.field is not self.checkbox_field.field):
+                yield widget
+
+    @property
+    def switchable(self):
+        return self.switch or self.checkbox_switch
+
+    @property
+    def switcher_state(self):
+        return 'on' if self.visible else 'off'
+
+    @property
+    def checker_state(self):
+        return 'on' if self.visible else 'off'
+
+
+def NamedWidgetsGroup(id, widgets, names=(), legend=None, help=None, css_class='', switch=False,
+                      checkbox_switch=False, checkbox_field=None, hide_if_empty=False):
+    """Create a widgets group based on widgets names"""
+    return FormWidgetsGroup(id, [widgets.get(name) for name in names], legend, help, css_class, switch,
+                            checkbox_switch, checkbox_field, hide_if_empty)
+
+
+@implementer(IGroupsBasedForm)
+class GroupsBasedForm(object):
+    """Groups based form
+
+    Should be used as a base class for forms also implementing IForm
+    """
+
+    def __init__(self):
+        self._groups = []
+
+    def add_group(self, group):
+        self._groups.append(group)
+
+    @property
+    def groups(self):
+        result = self._groups[:]
+        others = []
+        for widget in self.widgets.values():
+            found = False
+            for group in result:
+                if widget in group.widgets:
+                    found = True
+                    break
+            if not found:
+                others.append(widget)
+        if others:
+            result.insert(0, FormWidgetsGroup(None, others))
+        return result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/include.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+
+# import packages
+from pyams_form.form import FormSelector
+
+
+def include_package(config):
+    """Pyramid include"""
+
+    # add translations
+    config.add_translation_dirs('pyams_form:locales')
+
+    # custom subscriber predicate
+    config.add_subscriber_predicate('form_selector', FormSelector)
+
+    # load registry components
+    config.scan()
+
+    if hasattr(config, 'load_zcml'):
+        config.load_zcml('configure.zcml')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/interfaces/__init__.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+from .form import IForm, IFormLayer
+
+# import packages
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/interfaces/form.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,425 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+from pyams_template.template import template_config
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+from pyams_viewlet.interfaces import IViewletManager
+from pyramid.interfaces import IView
+from z3c.form.interfaces import INPUT_MODE, ISubForm, IWidget, IFormLayer as IBaseFormLayer, ISubmitWidget
+from zope.interface.interfaces import IObjectEvent, ObjectEvent
+from zope.lifecycleevent.interfaces import IObjectCreatedEvent, IObjectModifiedEvent
+
+# import packages
+from pyams_form.schema import ResetButton, CloseButton
+from z3c.form import button
+from zope.interface import implementer, Interface, Attribute
+from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
+from zope.schema import TextLine, List, Object, Bool, Choice, Dict
+
+from pyams_form import _
+
+
+#
+# Form interfaces
+#
+
+class IFormLayer(IBaseFormLayer):
+    """Base PyAMS form layer"""
+
+
+class IBaseForm(IView):
+    """Base form interface"""
+
+
+@template_config(template='../templates/form.pt', layer=IFormLayer)
+class IForm(IBaseForm):
+    """Default form interface"""
+
+    skin = TextLine(title="Skin name",
+                    default="PyAMS default skin",
+                    required=False)
+
+    edit_permission = TextLine(title="Required edit permission",
+                               required=False,
+                               default='manage')
+
+    def get_skin(self):
+        """Get skin associated with this form"""
+
+    title = TextLine(title="Form title")
+
+    legend = TextLine(title="Form legend",
+                      required=False)
+
+    css_class = TextLine(title="CSS class",
+                         default='ams-form form-horizontal')
+
+    icon_css_class = TextLine(title="Legend CSS class",
+                              required=False)
+
+    autocomplete = Choice(title="Auto-complete",
+                          values=('on', 'off'),
+                          default='on')
+
+    warn_on_change = Choice(title="Warn on unsaved change?",
+                            values=('default', True, False),
+                            default='default')
+
+    label_css_class = TextLine(title="Labels CSS class",
+                               required=False,
+                               default='control-label col-md-3')
+
+    input_css_class = TextLine(title="Inputs CSS class",
+                               required=False,
+                               default='col-md-9')
+
+    display_hints_on_widgets = Bool(title="Display hints on input widgets?",
+                                    required=True,
+                                    default=False)
+
+    handle_upload = Bool(title="Handle uploads in form?",
+                         description="Set to true when form handle uploads to get progress bar",
+                         required=True,
+                         default=False)
+
+    callbacks = Dict(title="Widgets validation callbacks",
+                     key_type=TextLine(),
+                     value_type=TextLine(),
+                     required=False)
+
+    subforms = List(title="Sub-forms",
+                    value_type=Object(schema=ISubForm),
+                    required=False,
+                    readonly=True)
+
+    subforms_legend = TextLine(title="Sub-forms legend",
+                               required=False)
+
+    tabforms = List(title="Tab-forms",
+                    value_type=Object(schema=ISubForm),
+                    required=False,
+                    readonly=True)
+
+    is_dialog = Attribute("Check to know if current form is in a modal dialog")
+
+    def get_form_action(self):
+        """Get form action URL"""
+
+    def get_widget_callback(self, widget):
+        """Get submit callback associated with a given widget"""
+
+    def update_content(self, object, data):
+        """Update given object with form data"""
+
+    def get_submit_output(self, writer, changes):
+        """Get submit output"""
+
+
+class IAJAXForm(Interface):
+    """AJAX form attributes"""
+
+    ajax_handler = TextLine(title="AJAX handler",
+                            description="Name of a JSON view handling AJAX requests",
+                            required=False)
+
+    form_options = Dict(title="AJAX data options",
+                        required=False)
+
+    form_target = TextLine(title="Form submit target",
+                           description="Form content target for HTML and text content types",
+                           required=False,
+                           default='#content')
+
+    ajax_callback = TextLine(title="AJAX submit callback",
+                             description="Name of a custom form submit callback",
+                             required=False)
+
+    def get_form_options(self):
+        """Get form options in JSON format"""
+
+    def get_ajax_handler(self):
+        """Get absolute URL of AJAX handler"""
+
+    def get_ajax_errors(self):
+        """Get AJAX errors"""
+
+    def get_ajax_output(self, changes):
+        """Get AJAX POST output"""
+
+
+class IFormWidgetsGroup(Interface):
+    """Form widgets group interface"""
+
+    id = TextLine(title="Group ID",
+                  required=False)
+
+    widgets = List(title="Group's widgets list",
+                   value_type=Object(schema=IWidget))
+
+    legend = TextLine(title="Group legend",
+                      required=False)
+
+    help = TextLine(title="Group help",
+                    required=False)
+
+    css_class = TextLine(title="CSS class",
+                         required=False,
+                         readonly=True)
+
+    switch = Bool(title="Switchable group?",
+                  required=True,
+                  default=False)
+
+    checkbox_switch = Bool(title="Group switched via checkbox?",
+                           required=True,
+                           default=False)
+
+    checkbox_field = TextLine(title="Field name matching switch checkbox?",
+                              required=False)
+
+    checkbox_widget = Object(schema=IWidget,
+                             required=False,
+                             readonly=True)
+
+    hide_if_empty = Bool(title="Hide group if empty?",
+                         description="""If 'Yes', a switchable group containing only """
+                                     """widgets with default values is hidden""",
+                         required=True,
+                         default=False)
+
+    visible = Attribute("Visible group?")
+
+    visible_widgets = Attribute("Visible widgets")
+
+    switchable = Attribute("Switchable group?")
+
+    switcher_state = Attribute("Switcher state")
+
+    checker_state = Attribute("Checker state")
+
+
+class IGroupsBasedForm(IBaseForm):
+    """Groups based form"""
+
+    groups = List(title="Form widgets groups",
+                  value_type=Object(IFormWidgetsGroup))
+
+    def add_group(self, group):
+        """Add given group to form groups"""
+
+
+class IViewletsBasedForm(IBaseForm):
+    """Viewlets based form"""
+
+    providers = List(title="List of content providers names included in this form",
+                     value_type=TextLine(),
+                     required=True)
+
+
+class IInnerSubForm(ISubForm):
+    """Inner sub-form interface"""
+
+
+class IInnerTabForm(ISubForm):
+    """Inner tab-form interface"""
+
+
+class IViewletSubForm(ISubForm):
+    """Inner viewlet form interface"""
+
+    legend = Attribute("Sub-form legend")
+
+    switchable = Attribute("Can the sub-form be hidden ?")
+
+    visible = Attribute("Is the sub-form initially visible ?")
+
+    callbacks = Dict(title="Widgets callbacks",
+                     key_type=TextLine(),
+                     value_type=TextLine())
+
+    def get_widget_callback(self, widget):
+        """Get submit callback associated with a given widget"""
+
+
+class ICustomExtractSubForm(ISubForm):
+    """SubForm interface with custom extract method"""
+
+    def extract(self):
+        """Custom data and errors extraction method"""
+
+
+class ICustomUpdateSubForm(ISubForm):
+    """SubForm interface with custom update method"""
+
+    def update_content(self, object, data):
+        """Custom content update method"""
+
+
+class ISearchForm(Interface):
+    """Default search form interface"""
+
+    def get_search_results(self):
+        """Get search results"""
+
+
+#
+# Form viewlets
+#
+
+class IFormViewletsManager(IViewletManager):
+    """Base forms viewlets manager interface"""
+
+
+class IFormPrefixViewletsManager(IFormViewletsManager):
+    """Form prefix viewlets manager interface"""
+
+
+class IWidgetsPrefixViewletsManager(IFormViewletsManager):
+    """Form widgets prefix viewlets manager interface"""
+
+
+class IWidgetsSuffixViewletsManager(IFormViewletsManager):
+    """Form widgets suffix viewlets manager interface"""
+
+
+class IFormSuffixViewletsManager(IFormViewletsManager):
+    """Form suffix viewlets manager interface"""
+
+
+#
+# Form buttons
+#
+
+def check_submit_button(form):
+    """Check form and widgets mode before displaying submit button"""
+    if form.mode != INPUT_MODE:
+        return False
+    for widget in form.widgets.values():
+        if widget.mode == INPUT_MODE:
+            return True
+    if IForm.providedBy(form):
+        for subform in form.subforms:
+            for widget in subform.widgets.values():
+                if widget.mode == INPUT_MODE:
+                    return True
+
+
+class IAddFormButtons(Interface):
+    """Default add form buttons"""
+
+    reset = ResetButton(name='reset', title=_("Reset"))
+    add = button.Button(name='add', title=_("Add"), condition=check_submit_button)
+
+
+class IModalAddFormButtons(Interface):
+    """Modal add form buttons"""
+
+    close = CloseButton(name='close', title=_("Close"))
+    add = button.Button(name='add', title=_("Add"), condition=check_submit_button)
+
+
+class IEditFormButtons(Interface):
+    """Default edit form buttons"""
+
+    reset = ResetButton(name='reset', title=_("Reset"))
+    submit = button.Button(name='submit', title=_("Submit"), condition=check_submit_button)
+
+
+class IModalEditFormButtons(Interface):
+    """Modal edit form buttons"""
+
+    close = CloseButton(name='close', title=_("Close"))
+    submit = button.Button(name='submit', title=_("Submit"), condition=check_submit_button)
+
+
+class IModalDisplayFormButtons(Interface):
+    """Modal display form buttons"""
+
+    close = CloseButton(name='close', title=_("Close"))
+
+
+#
+# Inner form interface
+#
+
+@template_config(template='../templates/inner-form.pt', layer=IFormLayer)
+class IInnerForm(Interface):
+    """Inner form marker interface"""
+
+
+@template_config(template='../templates/widget-form.pt', layer=IFormLayer)
+class IWidgetForm(Interface):
+    """Widget form marker interface"""
+
+    widget_icon_class = TextLine(title="Widget icon class",
+                                 default="fa fa-edit")
+
+
+#
+# Form buttons widgets
+#
+
+class IResetWidget(ISubmitWidget):
+    """Reset button widget interface"""
+
+
+class ICloseWidget(ISubmitWidget):
+    """Close button widget interface"""
+
+
+#
+# Form events
+#
+
+class IFormCreatedEvent(IObjectEvent):
+    """Form created event interface"""
+
+
+@implementer(IFormCreatedEvent)
+class FormCreatedEvent(ObjectEvent):
+    """Form created event"""
+
+
+class IViewObjectEvent(IObjectEvent):
+    """View object event interface"""
+
+    view = Attribute("View in which event was fired")
+
+
+class IFormObjectCreatedEvent(IObjectCreatedEvent, IViewObjectEvent):
+    """Event fired after final object creation"""
+
+
+@implementer(IFormObjectCreatedEvent)
+class FormObjectCreatedEvent(ObjectCreatedEvent):
+    """Form object created event"""
+
+    def __init__(self, object, view):
+        self.object = object
+        self.view = view
+
+
+class IFormObjectModifiedEvent(IObjectModifiedEvent, IViewObjectEvent):
+    """Event fired after object modification"""
+
+
+@implementer(IFormObjectModifiedEvent)
+class FormObjectModifiedEvent(ObjectModifiedEvent):
+    """Form object modified event"""
+
+    def __init__(self, object, view, *descriptions):
+        ObjectModifiedEvent.__init__(self, object, *descriptions)
+        self.view = view
Binary file src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.mo has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,78 @@
+#
+# French translations for PACKAGE package
+# This file is distributed under the same license as the PACKAGE package.
+# Thierry Florac <tflorac@ulthar.net>, 2015.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE 1.0\n"
+"POT-Creation-Date: 2015-01-29 22:41+0100\n"
+"PO-Revision-Date: 2015-01-29 17:17+0100\n"
+"Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
+"Language-Team: French <traduc@traduc.org>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Lingua 3.8\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: src/pyams_form/terms.py:36
+msgid "yes"
+msgstr "oui"
+
+#: src/pyams_form/terms.py:37
+msgid "no"
+msgstr "non"
+
+#: src/pyams_form/form.py:212
+msgid "Add form"
+msgstr ""
+
+#: src/pyams_form/form.py:213 src/pyams_form/form.py:264
+msgid "There were some errors."
+msgstr "Des erreurs se sont produites."
+
+#: src/pyams_form/form.py:263
+msgid "Edit form"
+msgstr ""
+
+#: src/pyams_form/form.py:265
+msgid "Data successfully updated."
+msgstr "Les modifications ont été enregistrées."
+
+#: src/pyams_form/form.py:266
+msgid "No changes were applied."
+msgstr "Aucune modification effectuée."
+
+#: src/pyams_form/form.py:353
+msgid "My legend"
+msgstr "Ma légende"
+
+#: src/pyams_form/templates/radio-input.pt:7
+msgid "Label"
+msgstr ""
+
+#: src/pyams_form/templates/inner-form.pt:89
+msgid "legend"
+msgstr ""
+
+#: src/pyams_form/templates/inner-form.pt:108
+msgid "Tab label"
+msgstr ""
+
+#: src/pyams_form/interfaces/form.py:295 src/pyams_form/interfaces/form.py:309
+msgid "Reset"
+msgstr "Annuler"
+
+#: src/pyams_form/interfaces/form.py:296 src/pyams_form/interfaces/form.py:303
+msgid "Add"
+msgstr "Ajouter"
+
+#: src/pyams_form/interfaces/form.py:302 src/pyams_form/interfaces/form.py:316
+#: src/pyams_form/interfaces/form.py:323
+msgid "Close"
+msgstr "Fermer"
+
+#: src/pyams_form/interfaces/form.py:310 src/pyams_form/interfaces/form.py:317
+msgid "Submit"
+msgstr "Enregistrer"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po~	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,74 @@
+#
+# French translations for PACKAGE package
+# This file is distributed under the same license as the PACKAGE package.
+# Thierry Florac <tflorac@ulthar.net>, 2015.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE 1.0\n"
+"POT-Creation-Date: 2015-01-29 17:29+0100\n"
+"PO-Revision-Date: 2015-01-29 17:17+0100\n"
+"Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
+"Language-Team: French <traduc@traduc.org>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Lingua 3.8\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: src/pyams_form/terms.py:36
+msgid "yes"
+msgstr "oui"
+
+#: src/pyams_form/terms.py:37
+msgid "no"
+msgstr "non"
+
+#: src/pyams_form/form.py:205
+msgid "Add form"
+msgstr ""
+
+#: src/pyams_form/form.py:206 src/pyams_form/form.py:257
+msgid "There were some errors."
+msgstr ""
+
+#: src/pyams_form/form.py:256
+msgid "Edit form"
+msgstr ""
+
+#: src/pyams_form/form.py:258
+msgid "Data successfully updated."
+msgstr ""
+
+#: src/pyams_form/form.py:259
+msgid "No changes were applied."
+msgstr ""
+
+#: src/pyams_form/form.py:340
+msgid "My legend"
+msgstr "Ma légende"
+
+#: src/pyams_form/templates/inner-form.pt:88
+msgid "legend"
+msgstr ""
+
+#: src/pyams_form/templates/inner-form.pt:107
+msgid "Tab label"
+msgstr ""
+
+#: src/pyams_form/interfaces/form.py:292 src/pyams_form/interfaces/form.py:306
+msgid "Reset"
+msgstr ""
+
+#: src/pyams_form/interfaces/form.py:293 src/pyams_form/interfaces/form.py:300
+msgid "Add"
+msgstr "Ajouter"
+
+#: src/pyams_form/interfaces/form.py:299 src/pyams_form/interfaces/form.py:313
+#: src/pyams_form/interfaces/form.py:320
+msgid "Close"
+msgstr ""
+
+#: src/pyams_form/interfaces/form.py:307 src/pyams_form/interfaces/form.py:314
+msgid "Submit"
+msgstr ""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/locales/pyams_form.pot	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,82 @@
+# 
+# SOME DESCRIPTIVE TITLE
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE 1.0\n"
+"POT-Creation-Date: 2015-01-29 22:41+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Lingua 3.8\n"
+
+#: ./src/pyams_form/terms.py:36
+msgid "yes"
+msgstr ""
+
+#: ./src/pyams_form/terms.py:37
+msgid "no"
+msgstr ""
+
+#: ./src/pyams_form/form.py:212
+msgid "Add form"
+msgstr ""
+
+#: ./src/pyams_form/form.py:213 ./src/pyams_form/form.py:264
+msgid "There were some errors."
+msgstr ""
+
+#: ./src/pyams_form/form.py:263
+msgid "Edit form"
+msgstr ""
+
+#: ./src/pyams_form/form.py:265
+msgid "Data successfully updated."
+msgstr ""
+
+#: ./src/pyams_form/form.py:266
+msgid "No changes were applied."
+msgstr ""
+
+#: ./src/pyams_form/form.py:353
+msgid "My legend"
+msgstr ""
+
+#: ./src/pyams_form/templates/radio-input.pt:7
+msgid "Label"
+msgstr ""
+
+#: ./src/pyams_form/templates/inner-form.pt:89
+msgid "legend"
+msgstr ""
+
+#: ./src/pyams_form/templates/inner-form.pt:108
+msgid "Tab label"
+msgstr ""
+
+#: ./src/pyams_form/interfaces/form.py:295
+#: ./src/pyams_form/interfaces/form.py:309
+msgid "Reset"
+msgstr ""
+
+#: ./src/pyams_form/interfaces/form.py:296
+#: ./src/pyams_form/interfaces/form.py:303
+msgid "Add"
+msgstr ""
+
+#: ./src/pyams_form/interfaces/form.py:302
+#: ./src/pyams_form/interfaces/form.py:316
+#: ./src/pyams_form/interfaces/form.py:323
+msgid "Close"
+msgstr ""
+
+#: ./src/pyams_form/interfaces/form.py:310
+#: ./src/pyams_form/interfaces/form.py:317
+msgid "Submit"
+msgstr ""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/schema.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from z3c.form.interfaces import IButton
+
+# import packages
+from z3c.form.button import Button
+from zope.interface import implementer
+
+
+class IResetButton(IButton):
+    """Reset button interface"""
+
+
+@implementer(IResetButton)
+class ResetButton(Button):
+    """Reset button"""
+
+
+class ICloseButton(IButton):
+    """Close button interface"""
+
+
+@implementer(ICloseButton)
+class CloseButton(Button):
+    """Close button"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/search.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,106 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_form.interfaces.form import IWidgetForm, ISearchForm
+from pyams_skin.interfaces import IContentSearch, IInnerPage, ISearchPage
+from pyams_skin.layer import IPyAMSLayer
+from z3c.table.interfaces import IValues
+
+# import packages
+from pyams_form.form import AddForm
+from pyams_form.schema import ResetButton
+from pyams_skin.table import BaseTable
+from pyams_template.template import template_config
+from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
+from pyramid.response import Response
+from z3c.form import field, button
+from zope.interface import implementer, Interface
+from zope.schema import TextLine
+
+from pyams_form import _
+
+
+class ISearchFields(Interface):
+    """Default search form interface"""
+
+    query = TextLine(title=_("Search query"))
+
+
+class ISearchButtons(Interface):
+    """Search form buttons interface"""
+
+    reset = ResetButton(name='reset', title=_("Reset"))
+    search = button.Button(name='search', title=_("Search"))
+
+
+@implementer(IWidgetForm, ISearchForm)
+class SearchForm(AddForm):
+    """Base search form"""
+
+    legend = _("Search")
+    widget_icon_class = 'fa fa-fw fa-search'
+
+    fields = field.Fields(ISearchFields)
+    buttons = button.Buttons(ISearchButtons)
+
+    ajax_handler = 'search-results.html'
+    form_target = '#search-results'
+
+    def updateActions(self):
+        super(SearchForm, self).updateActions()
+        if 'search' in self.actions:
+            self.actions['search'].addClass('btn-primary')
+
+    def get_search_results(self):
+        search = IContentSearch(self.context, None)
+        if search is not None:
+            self.updateWidgets()
+            data, errors = self.extractData()
+            return search.get_search_results(data)
+
+
+@template_config(template='templates/search.pt', layer=IPyAMSLayer)
+@implementer(IInnerPage, ISearchPage)
+class SearchView(object):
+    """Base search view"""
+
+    search_form_factory = SearchForm
+
+    def update(self):
+        self.search_form = self.search_form_factory(self.context, self.request)
+        self.search_form.update()
+
+
+class SearchResultsView(BaseTable):
+    """Search results view"""
+
+    search_form_factory = SearchForm
+
+    def __call__(self):
+        self.update()
+        return Response(self.renderTable())
+
+
+@adapter_config(context=(Interface, IPyAMSLayer, SearchResultsView), provides=IValues)
+class SearchResultsViewValuesAdapter(ContextRequestViewAdapter):
+    """Search results view values adapter"""
+
+    @property
+    def values(self):
+        form = self.view.search_form_factory(self.context, self.request)
+        return form.get_search_results()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/templates/form.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,141 @@
+<div class="modal-content">
+	<div class="modal-header">
+		<button type="button" class="close" data-dismiss="modal" aria-hidden="true"
+				tal:condition="view.is_dialog">
+			×
+		</button>
+		<h3 class="modal-title"
+			tal:define="config extension:configuration;">
+			<img tal:define="logo config.logo | nothing"
+				 tal:condition="logo"
+				 tal:attributes="src string:${extension:absolute_url(logo)}/++thumb++w200.png;
+								 alt config.logo_title;">
+			<span class="title" tal:content="view.title | context.title | config.title">Title</span>
+		</h3>
+	</div>
+	<div class="modal-body no-padding">
+		<div tal:define="prefix provider:form_prefix"
+			 tal:replace="structure prefix">Form prefix</div>
+		<form method="post"
+			  data-async
+			  tal:attributes="id view.id;
+							  name view.name;
+							  action view.get_form_action();
+							  method view.method;
+							  enctype view.enctype;
+							  acceptCharset view.acceptCharset;
+							  accept view.accept;
+							  autocomplete view.autocomplete;
+							  class view.css_class;
+							  data-ams-data extension:view_data;
+							  data-ams-form-handler view.get_ajax_handler() | nothing;
+							  data-ams-form-options view.get_form_options() | nothing;
+							  data-ams-form-submit-target view.form_target | nothing;
+							  data-ams-warn-on-change view.warn_on_change;">
+			<div class="modal-viewport">
+				<fieldset>
+					<legend tal:define="legend view.legend"
+							tal:condition="legend">
+						<i tal:attributes="class view.icon_css_class | nothing"></i>
+						<tal:var content="legend">Legend</tal:var>
+					</legend>
+					<tal:var content="structure provider:content_help" />
+					<div class="widgets-prefix"
+						 tal:define="prefix provider:widgets_prefix"
+						 tal:condition="prefix"
+						 tal:content="structure prefix">Widgets prefix</div>
+					<tal:loop repeat="group view.groups">
+						<fieldset tal:define="legend group.legend"
+								  tal:omit-tag="not:legend">
+							<tal:if condition="group.checkbox_switch">
+								<legend tal:condition="legend"
+										tal:content="legend"
+										tal:attributes="class group.css_class;
+														data-ams-checker-fieldname group.checker_field.getName();
+														data-ams-checker-state group.checker_state;">Legend</legend>
+							</tal:if>
+							<tal:if condition="not:group.checkbox_switch">
+								<legend tal:condition="legend"
+										tal:content="legend"
+										tal:attributes="class group.css_class;
+														data-ams-switcher-state group.switcher_state;">Legend</legend>
+							</tal:if>
+							<tal:var define="help group.help" condition="help">
+								<div class=""
+									 tal:define="html import:pyams_utils.text.text_to_html;
+												 i18n_help html(request.localizer.translate(help));"
+									 tal:content="structure i18n_help"></div>
+							</tal:var>
+							<tal:loop repeat="widget group.visible_widgets">
+								<input type="hidden"
+									   tal:condition="widget.mode == 'hidden'"
+									   tal:replace="structure widget.render()" />
+								<tal:if condition="widget.mode != 'hidden'">
+									<div tal:define="required 'required-field' if widget.required and (widget.mode != 'display') else ''"
+										 tal:attributes="class string:form-group ${required}">
+										<label tal:attributes="class view.label_css_class">
+											<span>
+												<tal:var content="widget.label" />
+												<i class="fa fa-question-circle hint" title="Input hint"
+												   tal:define="description widget.field.description"
+												   tal:condition="description"
+												   tal:attributes="title description;
+																   data-ams-hint-html '<' in description;"></i>
+											</span>
+										</label>
+										<div tal:attributes="class widget.input_css_class | view.input_css_class">
+											<label class="input"
+												   tal:attributes="class widget.label_css_class | default;
+																   data-ams-data extension:object_data(widget);
+																   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+												<input tal:replace="structure widget.render()" />
+											</label>
+										</div>
+									</div>
+								</tal:if>
+							</tal:loop>
+						</fieldset>
+					</tal:loop>
+					<div class="widgets-suffix"
+						 tal:define="suffix provider:widgets_suffix"
+						 tal:condition="suffix"
+						 tal:content="structure suffix">Widgets suffix</div>
+					<div class="subforms"
+						 tal:condition="view.subforms">
+						<fieldset tal:define="title view.subforms_legend"
+								  tal:omit-tag="not:title">
+							<legend tal:condition="title" tal:content="title" i18n:translate="">Title</legend>
+							<tal:loop repeat="subform view.subforms">
+								<tal:var replace="structure subform.render()" />
+							</tal:loop>
+						</fieldset>
+					</div>
+					<div class="tabforms"
+						 tal:condition="view.tabforms">
+						<ul class="nav nav-tabs">
+							<li tal:repeat="tabform view.tabforms"
+								tal:attributes="class tabform.widgets.errors and 'state-error' or ''">
+								<a data-toggle="tab"
+								   tal:attributes="href string:#${tabform.id}"
+								   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
+							</li>
+						</ul>
+						<div class="tab-content">
+							<div class="tab-pane fade in"
+								 tal:repeat="tabform view.tabforms">
+								 tal:attributes="id tabform.id"
+								 tal:content="structure tabform.render()" />
+							</div>
+						</div>
+					</div>
+				</fieldset>
+			</div>
+			<footer>
+				<button tal:repeat="action view.actions.values()"
+						tal:replace="structure action.render()">Action</button>
+			</footer>
+		</form>
+		<div tal:define="prefix provider:form_suffix"
+			 tal:replace="structure prefix">Form suffix</div>
+	</div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/templates/inner-form.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,125 @@
+<div class="no-padding">
+	<div tal:define="prefix provider:form_prefix"
+		 tal:replace="structure prefix">Form prefix</div>
+	<form method="post"
+		  data-async
+		  tal:attributes="id view.id;
+						  name view.name;
+						  action view.get_form_action();
+						  method view.method;
+						  enctype view.enctype;
+						  acceptCharset view.acceptCharset;
+						  accept view.accept;
+						  autocomplete view.autocomplete;
+						  class view.css_class;
+						  data-ams-data extension:view_data;
+						  data-ams-form-handler view.get_ajax_handler() | nothing;
+						  data-ams-form-options view.get_form_options() | nothing;
+						  data-ams-form-submit-target view.form_target | nothing;
+						  data-ams-warn-on-change view.warn_on_change;">
+		<div class="modal-viewport">
+			<fieldset>
+				<legend tal:define="legend view.legend"
+						tal:condition="legend">
+					<i tal:attributes="class view.icon_css_class | nothing"></i>
+					<tal:var content="legend">Legend</tal:var>
+				</legend>
+				<tal:var content="structure provider:content_help" />
+				<div class="widgets-prefix"
+					 tal:define="prefix provider:widgets_prefix"
+					 tal:condition="prefix"
+					 tal:content="structure prefix">Widgets prefix</div>
+				<tal:loop repeat="group view.groups">
+					<fieldset tal:define="legend group.legend"
+							  tal:omit-tag="not:legend">
+						<tal:if condition="group.checkbox_switch">
+							<legend tal:condition="legend"
+									tal:content="legend"
+									tal:attributes="class group.css_class;
+													data-ams-checker-fieldname group.checker_field.getName();
+													data-ams-checker-state group.checker_state;">Legend</legend>
+						</tal:if>
+						<tal:if condition="not:group.checkbox_switch">
+							<legend tal:condition="legend"
+									tal:content="legend"
+									tal:attributes="class group.css_class;
+													data-ams-switcher-state group.switcher_state;">Legend</legend>
+						</tal:if>
+						<tal:var define="help group.help" condition="help">
+							<div class=""
+								 tal:define="html import:pyams_utils.text.text_to_html;
+											 i18n_help html(request.localizer.translate(help));"
+								 tal:content="structure i18n_help"></div>
+						</tal:var>
+						<tal:loop repeat="widget group.visible_widgets">
+							<input type="hidden"
+								   tal:condition="python:widget.mode == 'hidden'"
+								   tal:replace="structure widget.render()" />
+							<tal:if condition="widget.mode != 'hidden'">
+								<div tal:define="required 'required-field' if widget.required and (widget.mode != 'display') else ''"
+									 tal:attributes="class string:form-group ${required}">
+									<label tal:attributes="class view.label_css_class">
+										<span>
+											<tal:var content="widget.label" />
+											<i class="fa fa-question-circle hint" title="Input hint"
+											   tal:define="description widget.field.description"
+											   tal:condition="description"
+											   tal:attributes="title description;
+															   data-ams-hint-html '<' in description;"></i>
+										</span>
+									</label>
+									<div tal:attributes="class widget.input_css_class | view.input_css_class">
+										<label class="input"
+											   tal:attributes="class widget.label_css_class | default;
+															   data-ams-data extension:object_data(widget);
+															   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+											<input tal:replace="structure widget.render()" />
+										</label>
+									</div>
+								</div>
+							</tal:if>
+						</tal:loop>
+					</fieldset>
+				</tal:loop>
+				<div class="widgets-suffix"
+					 tal:define="suffix provider:widgets_suffix"
+					 tal:condition="suffix"
+					 tal:content="structure suffix">Widgets suffix</div>
+				<div class="subforms"
+					 tal:condition="view.subforms">
+					<fieldset tal:define="title view.subforms_legend"
+							  tal:omit-tag="not:title">
+						<legend tal:condition="title" tal:content="title" i18n:translate="">legend</legend>
+						<tal:loop repeat="subform view.subforms">
+							<tal:var replace="structure subform.render()" />
+						</tal:loop>
+					</fieldset>
+				</div>
+				<div class="tabforms"
+					 tal:condition="view.tabforms">
+					<ul class="nav nav-tabs">
+						<li tal:repeat="tabform view.tabforms"
+							tal:attributes="class tabform.widgets.errors and 'state-error' or ''">
+							<a data-toggle="tab"
+							   tal:attributes="href string:#${tabform.id}"
+							   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
+						</li>
+					</ul>
+					<div class="tab-content">
+						<div class="tab-pane fade in"
+							 tal:repeat="tabform view.tabforms">
+							 tal:attributes="id tabform.id"
+							 tal:content="structure tabform.render()" />
+						</div>
+					</div>
+				</div>
+			</fieldset>
+		</div>
+		<footer>
+			<button tal:repeat="action view.actions.values()"
+					tal:replace="structure action.render()">Action</button>
+		</footer>
+	</form>
+	<div tal:define="prefix provider:form_prefix"
+		 tal:replace="structure prefix">Form prefix</div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/templates/search.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,13 @@
+<div id="search-form" data-ams-widget-sortable="false">
+	<tal:var content="structure view.search_form.render()" />
+	<div class="ams-widget hidden">
+		<header>
+			<span class="widget-icon"><i class="fa fa-flash"></i></span>
+			<h2>Search results</h2>
+		</header>
+		<div class="widget-body no-padding alerts-container">
+			<div class="widget-body-toolbar"></div>
+			<div id="search-results"></div>
+		</div>
+	</div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/templates/widget-form.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,129 @@
+<div class="ams-widget">
+	<header>
+		<span tal:condition="view.widget_icon_class | nothing"
+			  class="widget-icon"><i tal:attributes="class view.widget_icon_class"></i>
+		</span>
+		<h2 tal:content="view.legend"></h2>
+		<tal:var content="structure provider:pyams.toolbar" />
+	</header>
+	<div class="widget-body no-padding">
+		<div tal:define="prefix provider:form_prefix"
+			 tal:replace="structure prefix">Form prefix</div>
+		<tal:var content="structure provider:content_help" />
+		<form method="post"
+			  data-async
+			  tal:attributes="id view.id;
+							  name view.name;
+							  action view.get_form_action();
+							  method view.method;
+							  enctype view.enctype;
+							  acceptCharset view.acceptCharset;
+							  accept view.accept;
+							  autocomplete view.autocomplete;
+							  class view.css_class;
+							  data-ams-data extension:view_data;
+							  data-ams-form-handler view.get_ajax_handler() | nothing;
+							  data-ams-form-options view.get_form_options() | nothing;
+							  data-ams-form-submit-target view.form_target | nothing;
+							  data-ams-warn-on-change view.warn_on_change;">
+			<div class="modal-viewport">
+				<fieldset>
+					<div class="widgets-prefix"
+						 tal:define="prefix provider:widgets_prefix"
+						 tal:condition="prefix"
+						 tal:content="structure prefix">Widgets prefix</div>
+					<tal:loop repeat="group view.groups">
+						<fieldset tal:define="legend group.legend"
+								  tal:omit-tag="not:legend">
+							<tal:if condition="group.checkbox_switch">
+								<legend tal:condition="legend"
+										tal:content="legend"
+										tal:attributes="class group.css_class;
+														data-ams-checker-fieldname group.checker_field.getName();
+														data-ams-checker-state group.checker_state;">Legend</legend>
+							</tal:if>
+							<tal:if condition="not:group.checkbox_switch">
+								<legend tal:condition="legend"
+										tal:content="legend"
+										tal:attributes="class group.css_class;
+														data-ams-switcher-state group.switcher_state;">Legend</legend>
+							</tal:if>
+							<tal:var define="help group.help" condition="help">
+								<div class=""
+									 tal:define="html import:pyams_utils.text.text_to_html;
+												 i18n_help html(request.localizer.translate(help));"
+									 tal:content="structure i18n_help"></div>
+							</tal:var>
+							<tal:loop repeat="widget group.visible_widgets">
+								<input type="hidden"
+									   tal:condition="widget.mode == 'hidden'"
+									   tal:replace="structure widget.render()" />
+								<tal:if condition="widget.mode != 'hidden'">
+									<div tal:define="required 'required-field' if widget.required and (widget.mode != 'display') else ''"
+										 tal:attributes="class string:form-group ${required}">
+										<label tal:attributes="class view.label_css_class">
+											<span>
+												<tal:var content="widget.label" />
+												<i class="fa fa-question-circle hint" title="Input hint"
+												   tal:define="description widget.field.description"
+												   tal:condition="description"
+												   tal:attributes="title description;
+																   data-ams-hint-html '<' in description;"></i>
+											</span>
+										</label>
+										<div tal:attributes="class widget.input_css_class | view.input_css_class">
+											<label class="input"
+												   tal:attributes="class widget.label_css_class | default;
+																   data-ams-data extension:object_data(widget);
+																   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+												<input tal:replace="structure widget.render()" />
+											</label>
+										</div>
+									</div>
+								</tal:if>
+							</tal:loop>
+						</fieldset>
+					</tal:loop>
+					<div class="widgets-suffix"
+						 tal:define="suffix provider:widgets_suffix"
+						 tal:condition="suffix"
+						 tal:content="structure suffix">Widgets suffix</div>
+					<div class="subforms"
+						 tal:condition="view.subforms">
+						<fieldset tal:define="title view.subforms_legend"
+								  tal:omit-tag="not:title">
+							<legend tal:condition="title" tal:content="title" i18n:translate="">Title</legend>
+							<tal:loop repeat="subform view.subforms">
+								<tal:var replace="structure subform.render()" />
+							</tal:loop>
+						</fieldset>
+					</div>
+					<div class="tabforms"
+						 tal:condition="view.tabforms">
+						<ul class="nav nav-tabs">
+							<li tal:repeat="tabform view.tabforms"
+								tal:attributes="class tabform.widgets.errors and 'state-error' or ''">
+								<a data-toggle="tab"
+								   tal:attributes="href string:#${tabform.id}"
+								   tal:content="tabform.tabLabel" i18n:translate="">Tab label</a>
+							</li>
+						</ul>
+						<div class="tab-content">
+							<div class="tab-pane fade in"
+								 tal:repeat="tabform view.tabforms">
+								 tal:attributes="id tabform.id"
+								 tal:content="structure tabform.render()" />
+							</div>
+						</div>
+					</div>
+				</fieldset>
+			</div>
+			<footer>
+				<button tal:repeat="action view.actions.values()"
+						tal:replace="structure action.render()">Action</button>
+			</footer>
+		</form>
+		<div tal:define="prefix provider:form_suffix"
+			 tal:replace="structure prefix">Form suffix</div>
+	</div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/terms.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+from pyams_utils.adapter import adapter_config
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_form.interfaces.form import IFormLayer
+from z3c.form.interfaces import IWidget, IBoolTerms
+from zope.schema.interfaces import IBool
+
+# import packages
+from z3c.form.term import BoolTerms as BaseBoolTerms
+from zope.interface import implementer_only, Interface
+
+from pyams_utils import _
+
+
+@adapter_config(context=(Interface, IFormLayer, Interface, IBool, IWidget), provides=IBoolTerms)
+@implementer_only(IBoolTerms)
+class BoolTerms(BaseBoolTerms):
+    """Default yes and no terms are used by default for IBool fields."""
+
+    trueLabel = _('yes')
+    falseLabel = _('no')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/tests/__init__.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/tests/test_utilsdocs.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+"""
+Generic Test case for pyams_form doctest
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+import doctest
+import sys
+import os
+
+
+current_dir = os.path.dirname(__file__)
+
+def doc_suite(test_dir, setUp=None, tearDown=None, globs=None):
+    """Returns a test suite, based on doctests found in /doctest."""
+    suite = []
+    if globs is None:
+        globs = globals()
+
+    flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE |
+             doctest.REPORT_ONLY_FIRST_FAILURE)
+
+    package_dir = os.path.split(test_dir)[0]
+    if package_dir not in sys.path:
+        sys.path.append(package_dir)
+
+    doctest_dir = os.path.join(package_dir, 'doctests')
+
+    # filtering files on extension
+    docs = [os.path.join(doctest_dir, doc) for doc in
+            os.listdir(doctest_dir) if doc.endswith('.txt')]
+
+    for test in docs:
+        suite.append(doctest.DocFileSuite(test, optionflags=flags,
+                                          globs=globs, setUp=setUp,
+                                          tearDown=tearDown,
+                                          module_relative=False))
+
+    return unittest.TestSuite(suite)
+
+def test_suite():
+    """returns the test suite"""
+    return doc_suite(current_dir)
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/tests/test_utilsdocstrings.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+"""
+Generic Test case for pyams_form doc strings
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+import doctest
+import sys
+import os
+
+
+current_dir = os.path.abspath(os.path.dirname(__file__))
+
+def doc_suite(test_dir, globs=None):
+    """Returns a test suite, based on doc tests strings found in /*.py"""
+    suite = []
+    if globs is None:
+        globs = globals()
+
+    flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE |
+             doctest.REPORT_ONLY_FIRST_FAILURE)
+
+    package_dir = os.path.split(test_dir)[0]
+    if package_dir not in sys.path:
+        sys.path.append(package_dir)
+
+    # filtering files on extension
+    docs = [doc for doc in
+            os.listdir(package_dir) if doc.endswith('.py')]
+    docs = [doc for doc in docs if not doc.startswith('__')]
+
+    for test in docs:
+        fd = open(os.path.join(package_dir, test))
+        content = fd.read()
+        fd.close()
+        if '>>> ' not in content:
+            continue
+        test = test.replace('.py', '')
+        location = 'pyams_form.%s' % test
+        suite.append(doctest.DocTestSuite(location, optionflags=flags,
+                                          globs=globs))
+
+    return unittest.TestSuite(suite)
+
+def test_suite():
+    """returns the test suite"""
+    return doc_suite(current_dir)
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/viewlet.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,53 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_form.interfaces.form import IFormViewletsManager, IFormPrefixViewletsManager, IWidgetsPrefixViewletsManager, \
+    IWidgetsSuffixViewletsManager, IFormSuffixViewletsManager, IFormLayer
+
+# import packages
+from pyams_viewlet.manager import WeightOrderedViewletManager, viewletmanager_config
+from zope.interface import implementer
+
+
+@implementer(IFormViewletsManager)
+class FormViewletManager(WeightOrderedViewletManager):
+    """Base form viewlet manager"""
+
+
+@viewletmanager_config(name='form_prefix', layer=IFormLayer)
+@implementer(IFormPrefixViewletsManager)
+class FormPrefixViewletManager(FormViewletManager):
+    """Form prefix viewlet manager, displayed before form"""
+
+
+@viewletmanager_config(name='widgets_prefix', layer=IFormLayer)
+@implementer(IWidgetsPrefixViewletsManager)
+class WidgetsPrefixViewletManager(FormViewletManager):
+    """Form widgets prefix display manager, displayed before widgets"""
+
+
+@viewletmanager_config(name='widgets_suffix', layer=IFormLayer)
+@implementer(IWidgetsSuffixViewletsManager)
+class WidgetsSuffixViewletManager(FormViewletManager):
+    """Form widgets suffix viewlet manager, displayed after widgets"""
+
+
+@viewletmanager_config(name='form_suffix', layer=IFormLayer)
+@implementer(IFormSuffixViewletsManager)
+class FormSuffixViewletManager(FormViewletManager):
+    """Form suffix viewlet manager, displayed after form"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/__init__.py	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,197 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import inspect
+import os
+import venusian
+
+# import interfaces
+from pyams_form.interfaces.form import IFormLayer, IResetWidget, ICloseWidget
+from pyams_form.schema import IResetButton, ICloseButton
+from pyramid.interfaces import IRequest
+from z3c.form.interfaces import INPUT_MODE, IFieldWidget, IButtonAction, IWidgetLayoutTemplate
+from zope.pagetemplate.interfaces import IPageTemplate
+
+# import packages
+from pyams_utils.adapter import adapter_config
+from pyramid.exceptions import ConfigurationError
+from z3c.form.action import Action
+from z3c.form.browser.submit import SubmitWidget
+from z3c.form.button import ButtonAction
+from z3c.form.widget import FieldWidget, WidgetTemplateFactory, WidgetLayoutFactory
+from zope.interface import implementer_only, directlyProvides, Interface
+
+
+#
+# Widget template configuration
+#
+
+class widgettemplate_config(object):
+    """Class decorator used to declare a widget template"""
+
+    venusian = venusian  # for testing injection
+
+    def __init__(self, **settings):
+        if 'for_' in settings:
+            if settings.get('context') is None:
+                settings['context'] = settings['for_']
+        self.__dict__.update(settings)
+
+    def __call__(self, wrapped):
+        settings = self.__dict__.copy()
+
+        def callback(context, name, ob):
+            template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(ob))),
+                                    settings.get('template'))
+            if not os.path.isfile(template):
+                raise ConfigurationError("No such file", template)
+
+            contentType = settings.get('contentType', 'text/html')
+            factory = WidgetTemplateFactory(template, contentType)
+            provides = settings.get('provides', IPageTemplate)
+            directlyProvides(factory, provides)
+
+            config = context.config.with_package(info.module)
+            config.registry.registerAdapter(factory,
+                                            (settings.get('context', Interface),
+                                             settings.get('layer', IRequest),
+                                             settings.get('view', None),
+                                             settings.get('field', None),
+                                             settings.get('widget', ob)),
+                                            provides,
+                                            settings.get('mode', INPUT_MODE))
+
+        info = self.venusian.attach(wrapped, callback, category='pyams_form')
+
+        if info.scope == 'class':
+            # if the decorator was attached to a method in a class, or
+            # otherwise executed at class scope, we need to set an
+            # 'attr' into the settings if one isn't already in there
+            if settings.get('attr') is None:
+                settings['attr'] = wrapped.__name__
+
+        settings['_info'] = info.codeinfo  # fbo "action_method"
+        return wrapped
+
+
+class widgetlayout_config(object):
+    """Class decorator used to declare a widget layout"""
+
+    venusian = venusian  # for testing injection
+
+    def __init__(self, **settings):
+        if 'for_' in settings:
+            if settings.get('context') is None:
+                settings['context'] = settings['for_']
+        self.__dict__.update(settings)
+
+    def __call__(self, wrapped):
+        settings = self.__dict__.copy()
+
+        def callback(context, name, ob):
+            template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(ob))),
+                                    settings.get('template'))
+            if not os.path.isfile(template):
+                raise ConfigurationError("No such file", template)
+
+            contentType = settings.get('contentType', 'text/html')
+            factory = WidgetLayoutFactory(template, contentType)
+            provides = settings.get('provides', IWidgetLayoutTemplate)
+            directlyProvides(factory, provides)
+
+            config = context.config.with_package(info.module)
+            config.registry.registerAdapter(factory,
+                                            (settings.get('context', Interface),
+                                             settings.get('layer', IRequest),
+                                             settings.get('view', None),
+                                             settings.get('field', None),
+                                             settings.get('widget', ob)),
+                                            provides,
+                                            settings.get('mode', INPUT_MODE))
+
+        info = self.venusian.attach(wrapped, callback, category='pyams_form')
+
+        if info.scope == 'class':
+            # if the decorator was attached to a method in a class, or
+            # otherwise executed at class scope, we need to set an
+            # 'attr' into the settings if one isn't already in there
+            if settings.get('attr') is None:
+                settings['attr'] = wrapped.__name__
+
+        settings['_info'] = info.codeinfo  # fbo "action_method"
+        return wrapped
+
+
+#
+# Reset button widget and action
+#
+
+@widgettemplate_config(mode='input', template='templates/reset-input.pt', layer=IFormLayer)
+@widgettemplate_config(mode='display', template='templates/reset-display.pt', layer=IFormLayer)
+@implementer_only(IResetWidget)
+class ResetWidget(SubmitWidget):
+    """A reset button of a form."""
+
+    klass = u'reset-widget'
+    css = u'reset'
+
+
+@adapter_config(context=(IResetButton, IFormLayer), provides=IFieldWidget)
+def ResetFieldWidget(field, request):
+    reset = FieldWidget(field, ResetWidget(request))
+    reset.value = field.title
+    return reset
+
+
+@adapter_config(context=(IFormLayer, IResetButton), provides=IButtonAction)
+class ResetButtonAction(ResetWidget, ButtonAction):
+    """Reset button action"""
+
+    def __init__(self, request, field):
+        Action.__init__(self, request, field.title)
+        ResetWidget.__init__(self, request)
+        self.field = field
+
+
+#
+# Close button widget and action
+#
+
+@widgettemplate_config(mode='input', template='templates/close-input.pt', layer=IFormLayer)
+@widgettemplate_config(mode='display', template='templates/close-display.pt', layer=IFormLayer)
+@implementer_only(ICloseWidget)
+class CloseWidget(SubmitWidget):
+    """A dialog close button"""
+
+    klass = u'close-widget'
+    css = u'close'
+
+
+@adapter_config(context=(ICloseButton, IFormLayer), provides=IFieldWidget)
+def CloseFieldWidget(field, request):
+    close = FieldWidget(field, CloseWidget(request))
+    close.value = field.title
+    return close
+
+
+@adapter_config(context=(IFormLayer, ICloseButton), provides=IButtonAction)
+class CloseButtonAction(CloseWidget, ButtonAction):
+    """Close button action"""
+
+    def __init__(self, request, field):
+        Action.__init__(self, request, field.title)
+        CloseWidget.__init__(self, request)
+        self.field = field
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/configure.zcml	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,62 @@
+<configure
+	xmlns="http://pylonshq.com/pyramid"
+	xmlns:z3c="http://namespaces.zope.org/z3c">
+
+	<include package="pyramid_zcml" />
+	<include package="z3c.form" file="meta.zcml" />
+
+
+	<!-- Widgets templates -->
+	<z3c:widgetTemplate
+		mode="input"
+		template="templates/button-input.pt"
+		widget="z3c.form.interfaces.IButtonWidget"
+		layer="pyams_form.interfaces.form.IFormLayer" />
+
+	<z3c:widgetTemplate
+		mode="display"
+		template="templates/button-display.pt"
+		widget="z3c.form.interfaces.IButtonWidget"
+		layer="pyams_form.interfaces.form.IFormLayer" />
+
+
+	<z3c:widgetTemplate
+		mode="input"
+		template="templates/submit-input.pt"
+		widget="z3c.form.interfaces.ISubmitWidget"
+		layer="pyams_form.interfaces.form.IFormLayer" />
+
+	<z3c:widgetTemplate
+		mode="display"
+		template="templates/submit-display.pt"
+		widget="z3c.form.interfaces.ISubmitWidget"
+		layer="pyams_form.interfaces.form.IFormLayer" />
+
+
+	<z3c:widgetTemplate
+		mode="display"
+		template="templates/text-display.pt"
+		widget="z3c.form.interfaces.ITextWidget"
+		layer="pyams_form.interfaces.form.IFormLayer" />
+
+
+	<z3c:widgetTemplate
+		mode="input"
+		template="templates/radio-input.pt"
+		widget="z3c.form.interfaces.IRadioWidget"
+		layer="pyams_form.interfaces.form.IFormLayer" />
+
+	<z3c:widgetTemplate
+		mode="display"
+		template="templates/radio-display.pt"
+		widget="z3c.form.interfaces.IRadioWidget"
+		layer="pyams_form.interfaces.form.IFormLayer" />
+
+
+	<z3c:widgetTemplate
+		mode="input"
+		widget="z3c.form.interfaces.ISelectWidget"
+		layer="pyams_form.interfaces.form.IFormLayer"
+		template="templates/select-input.pt" />
+
+</configure>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/button-input.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,35 @@
+<button type="button"
+		id=""
+		name=""
+		class=""
+		value=""
+		accesskey=""
+		tal:define="value python:request.localizer.translate(view.value)"
+		tal:attributes="id view/id;
+						name view/name;
+						class string:btn ${view/klass};
+						style view/style;
+						lang view/lang;
+						onclick view/onclick;
+						ondblclick view/ondblclick;
+						onmousedown view/onmousedown;
+						onmouseup view/onmouseup;
+						onmouseover view/onmouseover;
+						onmousemove view/onmousemove;
+						onmouseout view/onmouseout;
+						onkeypress view/onkeypress;
+						onkeydown view/onkeydown;
+						onkeyup view/onkeyup;
+						value view/value;
+						disabled view/disabled;
+						tabindex view/tabindex;
+						onfocus view/onfocus;
+						onblur view/onblur;
+						onchange view/onchange;
+						readonly view/readonly;
+						alt view/alt;
+						accesskey view/accesskey;
+						onselect view/onselect;
+						data-loading-text string:${value}...;
+						data-ams-data extension:view_data;"
+		tal:content="value"></button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/close-display.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,34 @@
+<button type="button"
+		id=""
+		name=""
+		class=""
+		value=""
+		accesskey=""
+		tal:define="value python:request.localizer.translate(view.value)"
+		tal:attributes="id view/id;
+						name view/name;
+						class string:btn ${view/klass};
+						style view/style;
+						lang view/lang;
+						onclick view/onclick;
+						ondblclick view/ondblclick;
+						onmousedown view/onmousedown;
+						onmouseup view/onmouseup;
+						onmouseover view/onmouseover;
+						onmousemove view/onmousemove;
+						onmouseout view/onmouseout;
+						onkeypress view/onkeypress;
+						onkeydown view/onkeydown;
+						onkeyup view/onkeyup;
+						value view/value;
+						disabled view/disabled;
+						tabindex view/tabindex;
+						onfocus view/onfocus;
+						onblur view/onblur;
+						onchange view/onchange;
+						readonly view/readonly;
+						alt view/alt;
+						accesskey view/accesskey;
+						onselect view/onselect"
+		data-dismiss="modal"
+		tal:content="value"></button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/close-input.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,35 @@
+<button type="button"
+		id=""
+		name=""
+		class=""
+		value=""
+		accesskey=""
+		tal:define="value python:request.localizer.translate(view.value)"
+		tal:attributes="id view/id;
+						name view/name;
+						class string:btn ${view/klass};
+						style view/style;
+						lang view/lang;
+						onclick view/onclick;
+						ondblclick view/ondblclick;
+						onmousedown view/onmousedown;
+						onmouseup view/onmouseup;
+						onmouseover view/onmouseover;
+						onmousemove view/onmousemove;
+						onmouseout view/onmouseout;
+						onkeypress view/onkeypress;
+						onkeydown view/onkeydown;
+						onkeyup view/onkeyup;
+						value view/value;
+						disabled view/disabled;
+						tabindex view/tabindex;
+						onfocus view/onfocus;
+						onblur view/onblur;
+						onchange view/onchange;
+						readonly view/readonly;
+						alt view/alt;
+						accesskey view/accesskey;
+						onselect view/onselect;
+						data-ams-data extension:view_data;"
+		data-dismiss="modal"
+		tal:content="value"></button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/radio-display.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,17 @@
+<input type="text" readonly
+	   tal:attributes="id view/id;
+					   class string:${view/klass} border-0;
+					   style view/style;
+					   title view/title;
+					   lang view/lang;
+					   onclick view/onclick;
+					   ondblclick view/ondblclick;
+					   onmousedown view/onmousedown;
+					   onmouseup view/onmouseup;
+					   onmouseover view/onmouseover;
+					   onmousemove view/onmousemove;
+					   onmouseout view/onmouseout;
+					   onkeypress view/onkeypress;
+					   onkeydown view/onkeydown;
+					   onkeyup view/onkeyup;
+					   value python:', '.join(view.displayValue)" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/radio-input.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,36 @@
+<div class="inline-group" i18n:domain="pyams_form">
+	<label class="radio"
+		   tal:repeat="item view/items">
+		<input type="radio"
+			   tal:define="checked item/checked"
+			   tal:attributes="id item/id;
+								name item/name;
+								class view/klass;
+								value item/value;
+								style view/style;
+								title view/title;
+								lang view/lang;
+								onclick view/onclick;
+								ondblclick view/ondblclick;
+								onmousedown view/onmousedown;
+								onmouseup view/onmouseup;
+								onmouseover view/onmouseover;
+								onmousemove view/onmousemove;
+								onmouseout view/onmouseout;
+								onkeypress view/onkeypress;
+								onkeydown view/onkeydown;
+								onkeyup view/onkeyup;
+								disabled view/disabled;
+								tabindex view/tabindex;
+								onfocus view/onfocus;
+								onblur view/onblur;
+								onchange view/onchange;
+								readonly view/readonly;
+								alt view/alt;
+								accesskey view/accesskey;
+								onselect view/onselect;
+								checked python: checked and 'checked' or None;
+								data-ams-data extension:view_data;" />
+		<i></i><span tal:replace="item/label" i18n:translate="">Label</span>
+	</label>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/reset-display.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,33 @@
+<button type="reset"
+		id=""
+		name=""
+		class=""
+		value=""
+		accesskey=""
+		tal:define="value python:request.localizer.translate(view.value)"
+		tal:attributes="id view/id;
+						name view/name;
+						class string:btn ${view/klass};
+						style view/style;
+						lang view/lang;
+						onclick view/onclick;
+						ondblclick view/ondblclick;
+						onmousedown view/onmousedown;
+						onmouseup view/onmouseup;
+						onmouseover view/onmouseover;
+						onmousemove view/onmousemove;
+						onmouseout view/onmouseout;
+						onkeypress view/onkeypress;
+						onkeydown view/onkeydown;
+						onkeyup view/onkeyup;
+						value view/value;
+						disabled view/disabled;
+						tabindex view/tabindex;
+						onfocus view/onfocus;
+						onblur view/onblur;
+						onchange view/onchange;
+						readonly view/readonly;
+						alt view/alt;
+						accesskey view/accesskey;
+						onselect view/onselect"
+		tal:content="value"></button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/reset-input.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,34 @@
+<button type="reset"
+		id=""
+		name=""
+		class=""
+		value=""
+		accesskey=""
+		tal:define="value python:request.localizer.translate(view.value)"
+		tal:attributes="id view/id;
+						name view/name;
+						class string:btn ${view/klass};
+						style view/style;
+						lang view/lang;
+						onclick view/onclick;
+						ondblclick view/ondblclick;
+						onmousedown view/onmousedown;
+						onmouseup view/onmouseup;
+						onmouseover view/onmouseover;
+						onmousemove view/onmousemove;
+						onmouseout view/onmouseout;
+						onkeypress view/onkeypress;
+						onkeydown view/onkeydown;
+						onkeyup view/onkeyup;
+						value view/value;
+						disabled view/disabled;
+						tabindex view/tabindex;
+						onfocus view/onfocus;
+						onblur view/onblur;
+						onchange view/onchange;
+						readonly view/readonly;
+						alt view/alt;
+						accesskey view/accesskey;
+						onselect view/onselect;
+						data-ams-data extension:view_data;"
+		tal:content="value"></button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/select-input.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,29 @@
+<select class="select2"
+		tal:attributes="id view/id;
+						name string:${view/name}:list;
+						class string:${view/klass} select2;
+						style view/style;
+						title view/title;
+						lang view/lang;
+						onclick view/onclick;
+						ondblclick view/ondblclick;
+						onmousedown view/onmousedown;
+						onmouseup view/onmouseup;
+						onmouseover view/onmouseover;
+						onmousemove view/onmousemove;
+						onmouseout view/onmouseout;
+						onkeypress view/onkeypress;
+						onkeydown view/onkeydown;
+						onkeyup view/onkeyup;
+						disabled view/disabled;
+						tabindex view/tabindex;
+						onfocus view/onfocus;
+						onblur view/onblur;
+						onchange view/onchange;
+						multiple view/multiple;
+						size view/size">
+	<option tal:repeat="entry view/items"
+			tal:attributes="value entry/value;
+						    selected python:entry['value'] in view.value;"
+			tal:content="entry/content"></option>
+</select>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/submit-input.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,35 @@
+<button type="submit"
+		id=""
+		name=""
+		class=""
+		value=""
+		accesskey=""
+		tal:define="value python:request.localizer.translate(view.value)"
+		tal:attributes="id view/id;
+						name view/name;
+						class string:btn ${view/klass};
+						style view/style;
+						lang view/lang;
+						onclick view/onclick;
+						ondblclick view/ondblclick;
+						onmousedown view/onmousedown;
+						onmouseup view/onmouseup;
+						onmouseover view/onmouseover;
+						onmousemove view/onmousemove;
+						onmouseout view/onmouseout;
+						onkeypress view/onkeypress;
+						onkeydown view/onkeydown;
+						onkeyup view/onkeyup;
+						value view/value;
+						disabled view/disabled;
+						tabindex view/tabindex;
+						onfocus view/onfocus;
+						onblur view/onblur;
+						onchange view/onchange;
+						readonly view/readonly;
+						alt view/alt;
+						accesskey view/accesskey;
+						onselect view/onselect;
+						data-loading-text string:${value}...;
+						data-ams-data extension:view_data;"
+		tal:content="value"></button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/widget/templates/text-display.pt	Thu Feb 19 10:55:46 2015 +0100
@@ -0,0 +1,17 @@
+<input type="text" readonly
+	   tal:attributes="id view/id;
+					   class string:${view/klass} border-0;
+					   style view/style;
+					   title view/title;
+					   lang view/lang;
+					   onclick view/onclick;
+					   ondblclick view/ondblclick;
+					   onmousedown view/onmousedown;
+					   onmouseup view/onmouseup;
+					   onmouseover view/onmouseover;
+					   onmousemove view/onmousemove;
+					   onmouseout view/onmouseout;
+					   onkeypress view/onkeypress;
+					   onkeydown view/onkeydown;
+					   onkeyup view/onkeyup;
+					   value view/value" />