# HG changeset patch # User Sylvain Thénault # Date 1287395226 -7200 # Node ID 308037210dabdbe5b3bc121c3cdf6527ba7ca892 # Parent fd059ab1a5027755a580c080675c8c898708a724# Parent 972bd504daf68eebaaa8b5bce866898569b56e97 backport stable diff -r 972bd504daf6 -r 308037210dab .hgtags --- a/.hgtags Mon Oct 18 11:04:19 2010 +0200 +++ b/.hgtags Mon Oct 18 11:47:06 2010 +0200 @@ -155,3 +155,7 @@ 1c01f9dffd64d507863c9f8f68e3585b7aa24374 cubicweb-debian-version-3.9.7-1 eed788018b595d46a55805bd8d2054c401812b2b cubicweb-version-3.9.8 e4dba8ae963701a36be94ae58c790bc97ba029bb cubicweb-debian-version-3.9.8-1 +0793fe84651be36f8de9b4faba3781436dc07be0 cubicweb-version-3.10.0 +9ef1347f8d99e7daad290738ef93aa894a2c03ce cubicweb-debian-version-3.10.0-1 +6c6859a676732c845af69f92e74d4aafae12f83a cubicweb-version-3.10.1 +3abb41c47925f8fc6e327164d0ceca3773503ef9 cubicweb-debian-version-3.10.1-1 diff -r 972bd504daf6 -r 308037210dab __pkginfo__.py --- a/__pkginfo__.py Mon Oct 18 11:04:19 2010 +0200 +++ b/__pkginfo__.py Mon Oct 18 11:47:06 2010 +0200 @@ -1,4 +1,4 @@ -# pylint: disable-msg=W0622,C0103 +# pylint: disable=W0622,C0103 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 9, 8) +numversion = (3, 10, 1) version = '.'.join(str(num) for num in numversion) description = "a repository of entities / relations for knowledge management" @@ -42,7 +42,7 @@ __depends__ = { 'logilab-common': '>= 0.51.0', 'logilab-mtconverter': '>= 0.8.0', - 'rql': '>= 0.26.2', + 'rql': '>= 0.27.0', 'yams': '>= 0.30.1', 'docutils': '>= 0.6', #gettext # for xgettext, msgcat, etc... @@ -52,7 +52,7 @@ 'Twisted': '', # XXX graphviz # server dependencies - 'logilab-database': '>= 1.3.0', + 'logilab-database': '>= 1.3.1', 'pysqlite': '>= 2.5.5', # XXX install pysqlite2 } diff -r 972bd504daf6 -r 308037210dab _exceptions.py --- a/_exceptions.py Mon Oct 18 11:04:19 2010 +0200 +++ b/_exceptions.py Mon Oct 18 11:47:06 2010 +0200 @@ -159,5 +159,5 @@ class ExecutionError(Exception): """server execution control error (already started, not running...)""" -# pylint: disable-msg=W0611 +# pylint: disable=W0611 from logilab.common.clcommands import BadCommandUsage diff -r 972bd504daf6 -r 308037210dab common/mail.py --- a/common/mail.py Mon Oct 18 11:04:19 2010 +0200 +++ b/common/mail.py Mon Oct 18 11:47:06 2010 +0200 @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """pre 3.6 bw compat""" -# pylint: disable-msg=W0614,W0401 +# pylint: disable=W0614,W0401 from warnings import warn warn('moved to cubicweb.mail', DeprecationWarning, stacklevel=2) from cubicweb.mail import * diff -r 972bd504daf6 -r 308037210dab common/mixins.py --- a/common/mixins.py Mon Oct 18 11:04:19 2010 +0200 +++ b/common/mixins.py Mon Oct 18 11:47:06 2010 +0200 @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """pre 3.6 bw compat""" -# pylint: disable-msg=W0614,W0401 +# pylint: disable=W0614,W0401 from warnings import warn warn('moved to cubicweb.mixins', DeprecationWarning, stacklevel=2) from cubicweb.mixins import * diff -r 972bd504daf6 -r 308037210dab common/mttransforms.py --- a/common/mttransforms.py Mon Oct 18 11:04:19 2010 +0200 +++ b/common/mttransforms.py Mon Oct 18 11:47:06 2010 +0200 @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """pre 3.6 bw compat""" -# pylint: disable-msg=W0614,W0401 +# pylint: disable=W0614,W0401 from warnings import warn warn('moved to cubicweb.mttransforms', DeprecationWarning, stacklevel=2) from cubicweb.mttransforms import * diff -r 972bd504daf6 -r 308037210dab common/tags.py --- a/common/tags.py Mon Oct 18 11:04:19 2010 +0200 +++ b/common/tags.py Mon Oct 18 11:47:06 2010 +0200 @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """pre 3.6 bw compat""" -# pylint: disable-msg=W0614,W0401 +# pylint: disable=W0614,W0401 from warnings import warn warn('moved to cubicweb.tags', DeprecationWarning, stacklevel=2) from cubicweb.tags import * diff -r 972bd504daf6 -r 308037210dab common/uilib.py --- a/common/uilib.py Mon Oct 18 11:04:19 2010 +0200 +++ b/common/uilib.py Mon Oct 18 11:47:06 2010 +0200 @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """pre 3.6 bw compat""" -# pylint: disable-msg=W0614,W0401 +# pylint: disable=W0614,W0401 from warnings import warn warn('moved to cubicweb.uilib', DeprecationWarning, stacklevel=2) from cubicweb.uilib import * diff -r 972bd504daf6 -r 308037210dab cwconfig.py --- a/cwconfig.py Mon Oct 18 11:04:19 2010 +0200 +++ b/cwconfig.py Mon Oct 18 11:47:06 2010 +0200 @@ -26,12 +26,7 @@ directories, such as cubes, instances, etc. to ease development with the framework. There are two running modes with *CubicWeb*: -* 'user', resources are searched / created in the user home directory: - - - instances are stored in :file:`~/etc/cubicweb.d` - - temporary files (such as pid file) in :file:`/tmp` - -* 'system', resources are searched / created in the system directories (eg +* **system**: resources are searched / created in the system directories (eg usually requiring root access): - instances are stored in :file:`/etc/cubicweb.d` @@ -40,28 +35,34 @@ where `` is the detected installation prefix ('/usr/local' for instance). +* **user**: resources are searched / created in the user home directory: + + - instances are stored in :file:`~/etc/cubicweb.d` + - temporary files (such as pid file) in :file:`/tmp` + + Notice that each resource path may be explicitly set using an environment variable if the default doesn't suit your needs. Here are the default resource directories that are affected according to mode: -* 'system': :: +* **system**: :: CW_INSTANCES_DIR = /etc/cubicweb.d/ CW_INSTANCES_DATA_DIR = /var/lib/cubicweb/instances/ CW_RUNTIME_DIR = /var/run/cubicweb/ -* 'user': :: +* **user**: :: CW_INSTANCES_DIR = ~/etc/cubicweb.d/ CW_INSTANCES_DATA_DIR = ~/etc/cubicweb.d/ CW_RUNTIME_DIR = /tmp -Cubes search path is also affected, see the :ref:Cube section. +Cubes search path is also affected, see the :ref:`Cube` section. -By default, the mode automatically set to 'user' if a :file:`.hg` directory is found -in the cubicweb package, else it's set to 'system'. You can force this by setting -the :envvar:`CW_MODE` environment variable to either 'user' or 'system' so you can +By default, the mode automatically set to `user` if a :file:`.hg` directory is found +in the cubicweb package, else it's set to `system`. You can force this by setting +the :envvar:`CW_MODE` environment variable to either `user` or `system` so you can easily: * use system wide installation but user specific instances and all, without root @@ -672,6 +673,7 @@ def __init__(self, debugmode=False): register_stored_procedures() ConfigurationMixIn.__init__(self) + self._cubes = None self.debugmode = debugmode self.adjust_sys_path() self.load_defaults() @@ -774,6 +776,31 @@ """ return None + _cubes = None + + def init_cubes(self, cubes): + assert self._cubes is None, self._cubes + self._cubes = self.reorder_cubes(cubes) + # load cubes'__init__.py file first + for cube in cubes: + __import__('cubes.%s' % cube) + self.load_site_cubicweb() + + def cubes(self): + """return the list of cubes used by this instance + + result is ordered from the top level cubes to inner dependencies + cubes + """ + assert self._cubes is not None, 'cubes not initialized' + return self._cubes + + def cubes_path(self): + """return the list of path to cubes used by this instance, from outer + most to inner most cubes + """ + return [self.cube_dir(p) for p in self.cubes()] + class CubicWebConfiguration(CubicWebNoAppConfiguration): """base class for cubicweb server and web configurations""" @@ -937,7 +964,6 @@ def __init__(self, appid, debugmode=False): self.appid = appid CubicWebNoAppConfiguration.__init__(self, debugmode) - self._cubes = None self.load_file_configuration(self.main_config_file()) def adjust_sys_path(self): @@ -964,33 +990,13 @@ return join(iddir, self.appid) def init_cubes(self, cubes): - assert self._cubes is None, self._cubes - self._cubes = self.reorder_cubes(cubes) - # load cubes'__init__.py file first - for cube in cubes: - __import__('cubes.%s' % cube) - self.load_site_cubicweb() + super(CubicWebConfiguration, self).init_cubes(cubes) # reload config file in cases options are defined in cubes __init__ # or site_cubicweb files self.load_file_configuration(self.main_config_file()) # configuration initialization hook self.load_configuration() - def cubes(self): - """return the list of cubes used by this instance - - result is ordered from the top level cubes to inner dependencies - cubes - """ - assert self._cubes is not None - return self._cubes - - def cubes_path(self): - """return the list of path to cubes used by this instance, from outer - most to inner most cubes - """ - return [self.cube_dir(p) for p in self.cubes()] - def add_cubes(self, cubes): """add given cubes to the list of used cubes""" if not isinstance(cubes, list): diff -r 972bd504daf6 -r 308037210dab cwvreg.py --- a/cwvreg.py Mon Oct 18 11:04:19 2010 +0200 +++ b/cwvreg.py Mon Oct 18 11:47:06 2010 +0200 @@ -196,7 +196,7 @@ from warnings import warn from logilab.common.decorators import cached, clear_cache -from logilab.common.deprecation import deprecated +from logilab.common.deprecation import deprecated, class_deprecated from logilab.common.modutils import cleanup_sys_modules from rql import RQLHelper @@ -389,6 +389,8 @@ for vid, views in self.items(): if vid[0] == '_': continue + views = [view for view in views + if not isinstance(view, class_deprecated)] try: view = self._select_best(views, req, rset=rset, **kwargs) if view.linkable(): @@ -421,6 +423,44 @@ VRegistry.REGISTRY_FACTORY['actions'] = ActionsRegistry +class CtxComponentsRegistry(CWRegistry): + def poss_visible_objects(self, *args, **kwargs): + """return an ordered list of possible components""" + context = kwargs.pop('context') + if kwargs.get('rset') is None: + cache = args[0] + else: + cache = kwargs['rset'] + try: + cached = cache.__components_cache + except AttributeError: + ctxcomps = super(CtxComponentsRegistry, self).poss_visible_objects( + *args, **kwargs) + cached = cache.__components_cache = {} + for component in ctxcomps: + cached.setdefault(component.cw_propval('context'), []).append(component) + thisctxcomps = cached.get(context, ()) + # XXX set context for bw compat (should now be taken by comp.render()) + for component in thisctxcomps: + component.cw_extra_kwargs['context'] = context + return thisctxcomps + +VRegistry.REGISTRY_FACTORY['ctxcomponents'] = CtxComponentsRegistry + + +class BwCompatCWRegistry(object): + def __init__(self, vreg, oldreg, redirecttoreg): + self.vreg = vreg + self.oldreg = oldreg + self.redirecto = redirecttoreg + + def __getattr__(self, attr): + warn('[3.10] you should now use the %s registry instead of the %s registry' + % (self.redirecto, self.oldreg), DeprecationWarning, stacklevel=2) + return getattr(self.vreg[self.redirecto], attr) + + def clear(self): pass + def initialization_completed(self): pass class CubicWebVRegistry(VRegistry): """Central registry for the cubicweb instance, extending the generic @@ -433,15 +473,23 @@ stored objects. Currently we have the following registries of objects known by the web instance (library may use some others additional registries): - * etypes - * views - * components - * actions - * forms - * formrenderers - * controllers, which are directly plugged into the application - object to handle request publishing XXX to merge with views - * contentnavigation XXX to merge with components? to kill? + * 'etypes', entity type classes + + * 'views', views and templates (e.g. layout views) + + * 'components', non contextual components, like magic search, url evaluators + + * 'ctxcomponents', contextual components like boxes and dynamic section + + * 'actions', contextual actions, eg links to display in predefined places in + the ui + + * 'forms', describing logic of HTML form + + * 'formrenderers', rendering forms to html + + * 'controllers', primary objects to handle request publishing, directly + plugged into the application """ def __init__(self, config, initlog=True): @@ -456,6 +504,8 @@ # don't clear rtags during test, this may cause breakage with # manually imported appobject modules CW_EVENT_MANAGER.bind('before-registry-reload', clear_rtag_objects) + self['boxes'] = BwCompatCWRegistry(self, 'boxes', 'ctxcomponents') + self['contentnavigation'] = BwCompatCWRegistry(self, 'contentnavigation', 'ctxcomponents') def setdefault(self, regid): try: @@ -713,7 +763,7 @@ vocab = pdef['vocabulary'] if vocab is not None: if callable(vocab): - vocab = vocab(key, None) # XXX need a req object + vocab = vocab(None) # XXX need a req object if not value in vocab: raise ValueError(_('unauthorized value')) return value @@ -751,7 +801,7 @@ def possible_actions(self, req, rset=None, **kwargs): return self["actions"].possible_actions(req, rest=rset, **kwargs) - @deprecated('[3.4] use vreg["boxes"].select_object(...)') + @deprecated('[3.4] use vreg["ctxcomponents"].select_object(...)') def select_box(self, oid, *args, **kwargs): return self['boxes'].select_object(oid, *args, **kwargs) diff -r 972bd504daf6 -r 308037210dab dataimport.py --- a/dataimport.py Mon Oct 18 11:04:19 2010 +0200 +++ b/dataimport.py Mon Oct 18 11:47:06 2010 +0200 @@ -81,10 +81,11 @@ from logilab.common.deprecation import deprecated from cubicweb.server.utils import eschema_eid +from cubicweb.server.ssplanner import EditedEntity def count_lines(stream_or_filename): if isinstance(stream_or_filename, basestring): - f = open(filename) + f = open(stream_or_filename) else: f = stream_or_filename f.seek(0) @@ -97,8 +98,8 @@ skipfirst=False, withpb=True): """same as ucsvreader but a progress bar is displayed as we iter on rows""" if isinstance(stream_or_path, basestring): - if not osp.exists(filepath): - raise Exception("file doesn't exists: %s" % filepath) + if not osp.exists(stream_or_path): + raise Exception("file doesn't exists: %s" % stream_or_path) stream = open(stream_or_path) else: stream = stream_or_path @@ -420,7 +421,6 @@ ObjectStore.__init__(self) if session is None: sys.exit('please provide a session of run this script with cubicweb-ctl shell and pass cnx as session') - session = cnx if not hasattr(session, 'set_pool'): # connection cnx = session @@ -612,8 +612,7 @@ entity = copy(entity) entity.cw_clear_relation_cache() self.metagen.init_entity(entity) - entity.update(kwargs) - entity.edited_attributes = set(entity) + entity.cw_edited.update(kwargs, skipsec=False) session = self.session self.source.add_entity(session, entity) self.source.add_info(session, entity, self.source, None, complete=False) @@ -651,6 +650,11 @@ class MetaGenerator(object): + META_RELATIONS = (META_RTYPES + - VIRTUAL_RTYPES + - set(('eid', 'cwuri', + 'is', 'is_instance_of', 'cw_source'))) + def __init__(self, session, baseurl=None): self.session = session self.source = session.repo.system_source @@ -669,25 +673,20 @@ #self.entity_rels = [] XXX not handled (YAGNI?) schema = session.vreg.schema rschema = schema.rschema - for rtype in META_RTYPES: - if rtype in ('eid', 'cwuri') or rtype in VIRTUAL_RTYPES: - continue + for rtype in self.META_RELATIONS: if rschema(rtype).final: self.etype_attrs.append(rtype) else: self.etype_rels.append(rtype) - if not schema._eid_index: - # test schema loaded from the fs - self.gen_is = self.test_gen_is - self.gen_is_instance_of = self.test_gen_is_instanceof @cached def base_etype_dicts(self, etype): entity = self.session.vreg['etypes'].etype_class(etype)(self.session) # entity are "surface" copied, avoid shared dict between copies del entity.cw_extra_kwargs + entity.cw_edited = EditedEntity(entity) for attr in self.etype_attrs: - entity[attr] = self.generate(entity, attr) + entity.cw_edited.attribute_edited(attr, self.generate(entity, attr)) rels = {} for rel in self.etype_rels: rels[rel] = self.generate(entity, rel) @@ -696,7 +695,7 @@ def init_entity(self, entity): entity.eid = self.source.create_eid(self.session) for attr in self.entity_attrs: - entity[attr] = self.generate(entity, attr) + entity.cw_edited.attribute_edited(attr, self.generate(entity, attr)) def generate(self, entity, rtype): return getattr(self, 'gen_%s' % rtype)(entity) @@ -709,26 +708,7 @@ def gen_modification_date(self, entity): return self.time - def gen_is(self, entity): - return entity.e_schema.eid - def gen_is_instance_of(self, entity): - eids = [] - for etype in entity.e_schema.ancestors() + [entity.e_schema]: - eids.append(entity.e_schema.eid) - return eids - def gen_created_by(self, entity): return self.session.user.eid def gen_owned_by(self, entity): return self.session.user.eid - - # implementations of gen_is / gen_is_instance_of to use during test where - # schema has been loaded from the fs (hence entity type schema eids are not - # known) - def test_gen_is(self, entity): - return eschema_eid(self.session, entity.e_schema) - def test_gen_is_instanceof(self, entity): - eids = [] - for eschema in entity.e_schema.ancestors() + [entity.e_schema]: - eids.append(eschema_eid(self.session, eschema)) - return eids diff -r 972bd504daf6 -r 308037210dab dbapi.py --- a/dbapi.py Mon Oct 18 11:04:19 2010 +0200 +++ b/dbapi.py Mon Oct 18 11:47:06 2010 +0200 @@ -313,19 +313,17 @@ # low level session data management ####################################### - def get_shared_data(self, key, default=None, pop=False): - """return value associated to `key` in shared data""" - return self.cnx.get_shared_data(key, default, pop) - - def set_shared_data(self, key, value, querydata=False): - """set value associated to `key` in shared data + def get_shared_data(self, key, default=None, pop=False, txdata=False): + """see :meth:`Connection.get_shared_data`""" + return self.cnx.get_shared_data(key, default, pop, txdata) - if `querydata` is true, the value will be added to the repository - session's query data which are cleared on commit/rollback of the current - transaction, and won't be available through the connexion, only on the - repository side. - """ - return self.cnx.set_shared_data(key, value, querydata) + def set_shared_data(self, key, value, txdata=False, querydata=None): + """see :meth:`Connection.set_shared_data`""" + if querydata is not None: + txdata = querydata + warn('[3.10] querydata argument has been renamed to txdata', + DeprecationWarning, stacklevel=2) + return self.cnx.set_shared_data(key, value, txdata) # server session compat layer ############################################# @@ -534,9 +532,8 @@ esubpath = list(subpath) esubpath.remove('views') esubpath.append(join('web', 'views')) - cubespath = [config.cube_dir(p) for p in cubes] - config.load_site_cubicweb(cubespath) - vpath = config.build_vregistry_path(reversed(cubespath), + config.init_cubes(cubes) + vpath = config.build_vregistry_path(reversed(config.cubes_path()), evobjpath=esubpath, tvobjpath=subpath) self.vreg.register_objects(vpath) @@ -593,13 +590,15 @@ else: from cubicweb.entity import Entity user = Entity(req, rset, row=0) - user['login'] = login # cache login + user.cw_attr_cache['login'] = login # cache login return user @check_not_closed def check(self): - """raise `BadConnectionId` if the connection is no more valid""" - self._repo.check_session(self.sessionid) + """raise `BadConnectionId` if the connection is no more valid, else + return its latest activity timestamp. + """ + return self._repo.check_session(self.sessionid) def _txid(self, cursor=None): # XXX could now handle various isolation level! # return a dict as bw compat trick @@ -616,20 +615,26 @@ self._repo.set_session_props(self.sessionid, props) @check_not_closed - def get_shared_data(self, key, default=None, pop=False): - """return value associated to `key` in shared data""" - return self._repo.get_shared_data(self.sessionid, key, default, pop) + def get_shared_data(self, key, default=None, pop=False, txdata=False): + """return value associated to key in the session's data dictionary or + session's transaction's data if `txdata` is true. + + If pop is True, value will be removed from the dictionnary. + + If key isn't defined in the dictionnary, value specified by the + `default` argument will be returned. + """ + return self._repo.get_shared_data(self.sessionid, key, default, pop, txdata) @check_not_closed - def set_shared_data(self, key, value, querydata=False): + def set_shared_data(self, key, value, txdata=False): """set value associated to `key` in shared data - if `querydata` is true, the value will be added to the repository + if `txdata` is true, the value will be added to the repository session's query data which are cleared on commit/rollback of the current - transaction, and won't be available through the connexion, only on the - repository side. + transaction. """ - return self._repo.set_shared_data(self.sessionid, key, value, querydata) + return self._repo.set_shared_data(self.sessionid, key, value, txdata) # meta-data accessors ###################################################### diff -r 972bd504daf6 -r 308037210dab debian/changelog --- a/debian/changelog Mon Oct 18 11:04:19 2010 +0200 +++ b/debian/changelog Mon Oct 18 11:47:06 2010 +0200 @@ -1,3 +1,15 @@ +cubicweb (3.10.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Fri, 15 Oct 2010 12:08:58 +0200 + +cubicweb (3.10.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Wed, 13 Oct 2010 22:18:39 +0200 + cubicweb (3.9.8-1) unstable; urgency=low * new upstream release diff -r 972bd504daf6 -r 308037210dab debian/control --- a/debian/control Mon Oct 18 11:04:19 2010 +0200 +++ b/debian/control Mon Oct 18 11:47:06 2010 +0200 @@ -33,7 +33,7 @@ Conflicts: cubicweb-multisources Replaces: cubicweb-multisources Provides: cubicweb-multisources -Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.3.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2 +Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.3.1), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2 Recommends: pyro (< 4.0.0), cubicweb-documentation (= ${source:Version}) Description: server part of the CubicWeb framework CubicWeb is a semantic web application framework. @@ -97,7 +97,7 @@ Package: cubicweb-common Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.51.0), python-yams (>= 0.30.1), python-rql (>= 0.26.3), python-lxml +Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.51.0), python-yams (>= 0.30.1), python-rql (>= 0.27.0), python-lxml Recommends: python-simpletal (>= 4.0), python-crypto Conflicts: cubicweb-core Replaces: cubicweb-core diff -r 972bd504daf6 -r 308037210dab debian/cubicweb-documentation.install.in --- a/debian/cubicweb-documentation.install.in Mon Oct 18 11:04:19 2010 +0200 +++ b/debian/cubicweb-documentation.install.in Mon Oct 18 11:47:06 2010 +0200 @@ -1,2 +1,3 @@ doc/book usr/share/doc/cubicweb-documentation +doc/html usr/share/doc/cubicweb-documentation debian/cubicweb-doc usr/share/doc-base/cubicweb-doc diff -r 972bd504daf6 -r 308037210dab debian/rules --- a/debian/rules Mon Oct 18 11:04:19 2010 +0200 +++ b/debian/rules Mon Oct 18 11:47:06 2010 +0200 @@ -41,7 +41,9 @@ dh_install -vi # cwctl in the cubicweb-ctl package rm -f debian/cubicweb-common/usr/share/pyshared/cubicweb/cwctl.py - + # wdoc in the cubicweb-web package + rm -rf debian/cubicweb-common/usr/share/cubicweb/cubes/shared/wdoc + rm -rf debian/cubicweb-common/usr/share/cubicweb/cubes/shared/data dh_lintian # Remove unittests directory (should be available in cubicweb-dev only) diff -r 972bd504daf6 -r 308037210dab devtools/__init__.py --- a/devtools/__init__.py Mon Oct 18 11:04:19 2010 +0200 +++ b/devtools/__init__.py Mon Oct 18 11:47:06 2010 +0200 @@ -35,30 +35,19 @@ # db auto-population configuration ############################################# -SYSTEM_ENTITIES = schema.SCHEMA_TYPES | set(( - 'CWGroup', 'CWUser', 'CWProperty', - 'Workflow', 'State', 'BaseTransition', 'Transition', 'WorkflowTransition', - 'TrInfo', 'SubWorkflowExitPoint', - )) - -SYSTEM_RELATIONS = schema.META_RTYPES | set(( - # workflow related - 'workflow_of', 'state_of', 'transition_of', 'initial_state', 'default_workflow', - 'allowed_transition', 'destination_state', 'from_state', 'to_state', - 'condition', 'subworkflow', 'subworkflow_state', 'subworkflow_exit', - 'custom_workflow', 'in_state', 'wf_info_for', - # cwproperty - 'for_user', - # schema definition - 'specializes', - 'relation_type', 'from_entity', 'to_entity', - 'constrained_by', 'cstrtype', 'widget', - 'read_permission', 'update_permission', 'delete_permission', 'add_permission', - # permission - 'in_group', 'require_group', 'require_permission', - # deducted from other relations - 'primary_email', - )) +SYSTEM_ENTITIES = (schema.SCHEMA_TYPES + | schema.INTERNAL_TYPES + | schema.WORKFLOW_TYPES + | set(('CWGroup', 'CWUser',)) + ) +SYSTEM_RELATIONS = (schema.META_RTYPES + | schema.WORKFLOW_RTYPES + | schema.WORKFLOW_DEF_RTYPES + | schema.SYSTEM_RTYPES + | schema.SCHEMA_TYPES + | set(('primary_email', # deducted from other relations + )) + ) # content validation configuration ############################################# @@ -166,6 +155,8 @@ sources = super(TestServerConfiguration, self).sources() if not sources: sources = DEFAULT_SOURCES + if 'admin' not in sources: + sources['admin'] = DEFAULT_SOURCES['admin'] return sources # web config methods needed here for cases when we use this config as a web diff -r 972bd504daf6 -r 308037210dab devtools/dataimport.py --- a/devtools/dataimport.py Mon Oct 18 11:04:19 2010 +0200 +++ b/devtools/dataimport.py Mon Oct 18 11:47:06 2010 +0200 @@ -1,4 +1,4 @@ -# pylint: disable-msg=W0614,W0401 +# pylint: disable=W0614,W0401 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # diff -r 972bd504daf6 -r 308037210dab devtools/devctl.py --- a/devtools/devctl.py Mon Oct 18 11:04:19 2010 +0200 +++ b/devtools/devctl.py Mon Oct 18 11:47:06 2010 +0200 @@ -122,8 +122,7 @@ from copy import deepcopy from cubicweb.i18n import add_msg from cubicweb.web import uicfg - from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES, CONSTRAINTS - no_context_rtypes = META_RTYPES | SYSTEM_RTYPES + from cubicweb.schema import NO_I18NCONTEXT, CONSTRAINTS w('# schema pot file, generated on %s\n' % datetime.now().strftime('%Y-%m-%d %H:%M:%S')) w('# \n') @@ -212,13 +211,13 @@ else: librschema = libschema.rschema(rtype) # add context information only for non-metadata rtypes - if rschema not in no_context_rtypes: + if rschema not in NO_I18NCONTEXT: libsubjects = librschema and librschema.subjects() or () for subjschema in rschema.subjects(): if not subjschema in libsubjects: add_msg(w, rtype, subjschema.type) if not (schema.rschema(rtype).final or rschema.symmetric): - if rschema not in no_context_rtypes: + if rschema not in NO_I18NCONTEXT: libobjects = librschema and librschema.objects() or () for objschema in rschema.objects(): if not objschema in libobjects: @@ -234,6 +233,8 @@ def _iter_vreg_objids(vreg, done): for reg, objdict in vreg.items(): + if reg in ('boxes', 'contentnavigation'): + continue for objects in objdict.values(): for obj in objects: objid = '%s_%s' % (reg, obj.__regid__) @@ -345,7 +346,7 @@ print 'when you are done, run "cubicweb-ctl i18ncube yourcube".' -class UpdateTemplateCatalogCommand(Command): +class UpdateCubeCatalogCommand(Command): """Update i18n catalogs for cubes. If no cube is specified, update catalogs of all registered cubes. """ @@ -782,7 +783,7 @@ print make_qunit_html(args[0], args[1:]) for cmdcls in (UpdateCubicWebCatalogCommand, - UpdateTemplateCatalogCommand, + UpdateCubeCatalogCommand, #LiveServerCommand, NewCubeCommand, ExamineLogCommand, diff -r 972bd504daf6 -r 308037210dab devtools/fake.py --- a/devtools/fake.py Mon Oct 18 11:04:19 2010 +0200 +++ b/devtools/fake.py Mon Oct 18 11:47:06 2010 +0200 @@ -170,6 +170,7 @@ self.config = config or FakeConfig() self.vreg = vreg or CubicWebVRegistry(self.config, initlog=False) self.vreg.schema = schema + self.sources = [] def internal_session(self): return FakeSession(self) diff -r 972bd504daf6 -r 308037210dab devtools/repotest.py --- a/devtools/repotest.py Mon Oct 18 11:04:19 2010 +0200 +++ b/devtools/repotest.py Mon Oct 18 11:47:06 2010 +0200 @@ -284,8 +284,7 @@ self.repo.vreg.rqlhelper.backend = 'postgres' # so FTIRANK is considered def add_source(self, sourcecls, uri): - self.sources.append(sourcecls(self.repo, self.o.schema, - {'uri': uri})) + self.sources.append(sourcecls(self.repo, {'uri': uri})) self.repo.sources_by_uri[uri] = self.sources[-1] setattr(self, uri, self.sources[-1]) self.newsources += 1 diff -r 972bd504daf6 -r 308037210dab devtools/testlib.py --- a/devtools/testlib.py Mon Oct 18 11:04:19 2010 +0200 +++ b/devtools/testlib.py Mon Oct 18 11:47:06 2010 +0200 @@ -37,7 +37,7 @@ from logilab.common.debugger import Debugger from logilab.common.umessage import message_from_string from logilab.common.decorators import cached, classproperty, clear_cache -from logilab.common.deprecation import deprecated +from logilab.common.deprecation import deprecated, class_deprecated from cubicweb import ValidationError, NoSelectableObject, AuthenticationError from cubicweb import cwconfig, devtools, web, server @@ -295,12 +295,15 @@ def set_debug(self, debugmode): server.set_debug(debugmode) + def debugged(self, debugmode): + return server.debugged(debugmode) + # default test setup and teardown ######################################### def setUp(self): # monkey patch send mail operation so emails are sent synchronously - self._old_mail_commit_event = SendMailOp.commit_event - SendMailOp.commit_event = SendMailOp.sendmails + self._old_mail_postcommit_event = SendMailOp.postcommit_event + SendMailOp.postcommit_event = SendMailOp.sendmails pause_tracing() previous_failure = self.__class__.__dict__.get('_repo_init_failed') if previous_failure is not None: @@ -322,7 +325,7 @@ for cnx in self._cnxs: if not cnx._closed: cnx.close() - SendMailOp.commit_event = self._old_mail_commit_event + SendMailOp.postcommit_event = self._old_mail_postcommit_event def setup_database(self): """add your database setup code by overriding this method""" @@ -492,7 +495,8 @@ continue views = [view for view in views if view.category != 'startupview' - and not issubclass(view, notification.NotificationView)] + and not issubclass(view, notification.NotificationView) + and not isinstance(view, class_deprecated)] if views: try: view = viewsvreg._select_best(views, req, rset=rset) @@ -514,7 +518,7 @@ def list_boxes_for(self, rset): """returns the list of boxes that can be applied on `rset`""" req = rset.req - for box in self.vreg['boxes'].possible_objects(req, rset=rset): + for box in self.vreg['ctxcomponents'].possible_objects(req, rset=rset): yield box def list_startup_views(self): @@ -738,6 +742,7 @@ msg = '[%s in %s] %s' % (klass, view.__regid__, exc) except: msg = '[%s in %s] undisplayable exception' % (klass, view.__regid__) + msg = str(msg) # ensure no unicode if output is not None: position = getattr(exc, "position", (0,))[0] if position: @@ -969,7 +974,8 @@ for action in self.list_actions_for(rset): yield InnerTest(self._testname(rset, action.__regid__, 'action'), self._test_action, action) for box in self.list_boxes_for(rset): - yield InnerTest(self._testname(rset, box.__regid__, 'box'), box.render) + w = [].append + yield InnerTest(self._testname(rset, box.__regid__, 'box'), box.render, w) @staticmethod def _testname(rset, objid, objtype): diff -r 972bd504daf6 -r 308037210dab doc/book/en/devrepo/devcore/cwconfig.rst --- a/doc/book/en/devrepo/devcore/cwconfig.rst Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -Configuration -------------- - -.. automodule:: cubicweb.cwconfig - :members: diff -r 972bd504daf6 -r 308037210dab doc/book/en/devrepo/devcore/index.rst --- a/doc/book/en/devrepo/devcore/index.rst Mon Oct 18 11:04:19 2010 +0200 +++ b/doc/book/en/devrepo/devcore/index.rst Mon Oct 18 11:47:06 2010 +0200 @@ -6,5 +6,4 @@ dbapi.rst reqbase.rst - cwconfig.rst diff -r 972bd504daf6 -r 308037210dab doc/book/en/devrepo/repo/hooks.rst --- a/doc/book/en/devrepo/repo/hooks.rst Mon Oct 18 11:04:19 2010 +0200 +++ b/doc/book/en/devrepo/repo/hooks.rst Mon Oct 18 11:47:06 2010 +0200 @@ -1,162 +1,27 @@ .. -*- coding: utf-8 -*- - .. _hooks: Hooks and Operations ==================== -Generalities ------------- - -Paraphrasing the `emacs`_ documentation, let us say that hooks are an -important mechanism for customizing an application. A hook is -basically a list of functions to be called on some well-defined -occasion (this is called `running the hook`). - -.. _`emacs`: http://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html - -In CubicWeb, hooks are subclasses of the Hook class in -`server/hook.py`, implementing their own `call` method, and selected -over a set of pre-defined `events` (and possibly more conditions, -hooks being selectable AppObjects like views and components). - -There are two families of events: data events and server events. In a -typical application, most of the Hooks are defined over data -events. - -The purpose of data hooks is to complement the data model as defined -in the schema.py, which is static by nature, with dynamic or value -driven behaviours. It is functionally equivalent to a `database -trigger`_, except that database triggers definition languages are not -standardized, hence not portable (for instance, PL/SQL works with -Oracle and PostgreSQL but not SqlServer nor Sqlite). - -.. _`database trigger`: http://en.wikipedia.org/wiki/Database_trigger - -Data hooks can serve the following purposes: - -* enforcing constraints that the static schema cannot express - (spanning several entities/relations, exotic value ranges and - cardinalities, etc.) - -* implement computed attributes - -Operations are Hook-like objects that may be created by Hooks and -scheduled to happen just before (or after) the `commit` event. Hooks -being fired immediately on data operations, it is sometime necessary -to delay the actual work down to a time where all other Hooks have -run, for instance a validation check which needs that all relations be -already set on an entity. Also while the order of execution of Hooks -is data dependant (and thus hard to predict), it is possible to force -an order on Operations. - -Operations also may be used to process various side effects associated -with a transaction such as filesystem udpates, mail notifications, -etc. - -Operations are subclasses of the Operation class in `server/hook.py`, -implementing `precommit_event` and other standard methods (wholly -described in :ref:`operations_api`). - -.. hint:: - - It is a good practice, to write unit tests for each hook. See an example in :ref:`hook_test` - -Events ------- - -Hooks are mostly defined and used to handle `dataflow`_ operations. It -means as data gets in (entities added, updated, relations set or -unset), specific events are issued and the Hooks matching these events -are called. - -.. _`dataflow`: http://en.wikipedia.org/wiki/Dataflow - -Below comes a list of the dataflow events related to entities operations: - -* before_add_entity - -* before_update_entity - -* before_delete_entity - -* after_add_entity - -* after_update_entity - -* after_delete_entity - -These define ENTTIES HOOKS. RELATIONS HOOKS are defined -over the following events: - -* after_add_relation - -* after_delete_relation - -* before_add_relation - -* before_delete_relation - -This is an occasion to remind us that relations support the add/delete -operation, but no update. - -Non data events also exist. These are called SYSTEM HOOKS. - -* server_startup - -* server_shutdown - -* server_maintenance - -* server_backup - -* server_restore - -* session_open - -* session_close +.. autodocstring:: cubicweb.server.hook -Using dataflow Hooks --------------------- - -Dataflow hooks either automate data operations or maintain the -consistency of the data model. In the later case, we must use a -specific exception named ValidationError - -Validation Errors -~~~~~~~~~~~~~~~~~ - -When a condition is not met in a Hook/Operation, it must raise a -`ValidationError`. Raising anything but a (subclass of) -ValidationError is a programming error. Raising a ValidationError -entails aborting the current transaction. +Example using dataflow hooks +---------------------------- -The ValidationError exception is used to convey enough information up -to the user interface. Hence its constructor is different from the -default Exception constructor. It accepts, positionally: - -* an entity eid, - -* a dict whose keys represent attribute (or relation) names and values - an end-user facing message (hence properly translated) relating the - problem. - -An entity hook -~~~~~~~~~~~~~~ - -We will use a very simple example to show hooks usage. Let us start -with the following schema. +We will use a very simple example to show hooks usage. Let us start with the +following schema. .. sourcecode:: python class Person(EntityType): age = Int(required=True) -We would like to add a range constraint over a person's age. Let's -write an hook. It shall be placed into mycube/hooks.py. If this file -were to grow too much, we can easily have a mycube/hooks/... package -containing hooks in various modules. +We would like to add a range constraint over a person's age. Let's write an hook +(supposing yams can not handle this nativly, which is wrong). It shall be placed +into `mycube/hooks.py`. If this file were to grow too much, we can easily have a +`mycube/hooks/... package` containing hooks in various modules. .. sourcecode:: python @@ -166,68 +31,30 @@ class PersonAgeRange(Hook): __regid__ = 'person_age_range' + __select__ = Hook.__select__ & is_instance('Person') events = ('before_add_entity', 'before_update_entity') - __select__ = Hook.__select__ & is_instance('Person') def __call__(self): - if 0 >= self.entity.age <= 120: - return - msg = self._cw._('age must be between 0 and 120') - raise ValidationError(self.entity.eid, {'age': msg}) - -Hooks being AppObjects like views, they have a __regid__ and a -__select__ class attribute. The base __select__ is augmented with an -`is_instance` selector matching the desired entity type. The `events` -tuple is used by the Hook.__select__ base selector to dispatch the -hook on the right events. In an entity hook, it is possible to -dispatch on any entity event (e.g. 'before_add_entity', -'before_update_entity') at once if needed. + if 'age' in self.entity.cw_edited: + if 0 <= self.entity.age <= 120: + return + msg = self._cw._('age must be between 0 and 120') + raise ValidationError(self.entity.eid, {'age': msg}) -Like all appobjects, hooks have the `self._cw` attribute which -represents the current session. In entity hooks, a `self.entity` -attribute is also present. - - -A relation hook -~~~~~~~~~~~~~~~ - -Let us add another entity type with a relation to person (in -mycube/schema.py). - -.. sourcecode:: python +In our example the base `__select__` is augmented with an `is_instance` selector +matching the desired entity type. - class Company(EntityType): - name = String(required=True) - boss = SubjectRelation('Person', cardinality='1*') +The `events` tuple is used specify that our hook should be called before the +entity is added or updated. -We would like to constrain the company's bosses to have a minimum -(legal) age. Let's write an hook for this, which will be fired when -the `boss` relation is established. - -.. sourcecode:: python - - class CompanyBossLegalAge(Hook): - __regid__ = 'company_boss_legal_age' - events = ('before_add_relation',) - __select__ = Hook.__select__ & match_rtype('boss') +Then in the hook's `__call__` method, we: - def __call__(self): - boss = self._cw.entity_from_eid(self.eidto) - if boss.age < 18: - msg = self._cw._('the minimum age for a boss is 18') - raise ValidationError(self.eidfrom, {'boss': msg}) - -We use the `match_rtype` selector to select the proper relation type. +* check if the 'age' attribute is edited +* if so, check the value is in the range +* if not, raise a validation error properly -The essential difference with respect to an entity hook is that there -is no self.entity, but `self.eidfrom` and `self.eidto` hook attributes -which represent the subject and object eid of the relation. - - -Using Operations ----------------- - -Let's augment our example with a new `subsidiary_of` relation on Company. +Now Let's augment our schema with new `Company` entity type with some relation to +`Person` (in 'mycube/schema.py'). .. sourcecode:: python @@ -236,12 +63,37 @@ boss = SubjectRelation('Person', cardinality='1*') subsidiary_of = SubjectRelation('Company', cardinality='*?') -Base example -~~~~~~~~~~~~ + +We would like to constrain the company's bosses to have a minimum (legal) +age. Let's write an hook for this, which will be fired when the `boss` relation +is established (still supposing we could not specify that kind of thing in the +schema). + +.. sourcecode:: python + + class CompanyBossLegalAge(Hook): + __regid__ = 'company_boss_legal_age' + __select__ = Hook.__select__ & match_rtype('boss') + events = ('before_add_relation',) -We would like to check that there is no cycle by the `subsidiary_of` -relation. This is best achieved in an Operation since all relations -are likely to be set at commit time. + def __call__(self): + boss = self._cw.entity_from_eid(self.eidto) + if boss.age < 18: + msg = self._cw._('the minimum age for a boss is 18') + raise ValidationError(self.eidfrom, {'boss': msg}) + +.. Note:: + + We use the :class:`~cubicweb.server.hook.match_rtype` selector to select the + proper relation type. + + The essential difference with respect to an entity hook is that there is no + self.entity, but `self.eidfrom` and `self.eidto` hook attributes which + represent the subject and object **eid** of the relation. + +Suppose we want to check that there is no cycle by the `subsidiary_of` +relation. This is best achieved in an operation since all relations are likely to +be set at commit time. .. sourcecode:: python @@ -257,6 +109,7 @@ raise ValidationError(eid, {rtype: msg}) parents.add(parent.eid) + class CheckSubsidiaryCycleOp(Operation): def precommit_event(self): @@ -265,30 +118,20 @@ class CheckSubsidiaryCycleHook(Hook): __regid__ = 'check_no_subsidiary_cycle' + __select__ = Hook.__select__ & match_rtype('subsidiary_of') events = ('after_add_relation',) - __select__ = Hook.__select__ & match_rtype('subsidiary_of') def __call__(self): CheckSubsidiaryCycleOp(self._cw, eidto=self.eidto) -The operation is instantiated in the Hook.__call__ method. -An operation always takes a session object as first argument -(accessible as `.session` from the operation instance), and optionally -all keyword arguments needed by the operation. These keyword arguments -will be accessible as attributes from the operation instance. +Like in hooks, :exc:`~cubicweb.ValidationError` can be raised in operations. Other +exceptions are usually programming errors. -Like in Hooks, ValidationError can be raised in Operations. Other -exceptions are programming errors. - -Notice how our hook will instantiate an operation each time the Hook -is called, i.e. each time the `subsidiary_of` relation is set. - -Using set_operation -~~~~~~~~~~~~~~~~~~~ - -There is an alternative method to schedule an Operation from a Hook, -using the `set_operation` function. +In the above example, our hook will instantiate an operation each time the hook +is called, i.e. each time the `subsidiary_of` relation is set. There is an +alternative method to schedule an operation from a hook, using the +:func:`set_operation` function. .. sourcecode:: python @@ -301,7 +144,7 @@ def __call__(self): set_operation(self._cw, 'subsidiary_cycle_detection', self.eidto, - CheckSubsidiaryCycleOp, rtype=self.rtype) + CheckSubsidiaryCycleOp) class CheckSubsidiaryCycleOp(Operation): @@ -309,134 +152,83 @@ for eid in self.session.transaction_data['subsidiary_cycle_detection']: check_cycle(self.session, eid, self.rtype) -Here, we call set_operation with a session object, a specially forged -key, a value that is the actual payload of an individual operation (in -our case, the object of the subsidiary_of relation) , the class of the -Operation, and more optional parameters to give to the operation (here -the rtype which do not vary accross operations). - -The body of the operation must then iterate over the values that have -been mapped in the transaction_data dictionary to the forged key. -This mechanism is especially useful on two occasions (not shown in our -example): +Here, we call :func:`set_operation` so that we will simply accumulate eids of +entities to check at the end in a single `CheckSubsidiaryCycleOp` +operation. Value are stored in a set associated to the +'subsidiary_cycle_detection' transaction data key. The set initialization and +operation creation are handled nicely by :func:`set_operation`. -* massive data import (reduced memory consumption within a large - transaction) +A more realistic example can be found in the advanced tutorial chapter +:ref:`adv_tuto_security_propagation`. -* when several hooks need to instantiate the same operation (e.g. an - entity and a relation hook). - -.. note:: - A more realistic example can be found in the advanced tutorial - chapter :ref:`adv_tuto_security_propagation`. - -.. _operations_api: - -Operation: a small API overview -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Hooks writing tips +------------------ -.. autoclass:: cubicweb.server.hook.Operation -.. autoclass:: cubicweb.server.hook.LateOperation -.. autofunction:: cubicweb.server.hook.set_operation +Reminder +~~~~~~~~ -Hooks writing rules -------------------- +Never, ever use the `entity.foo = 42` notation to update an entity. It will not +work.To updating an entity attribute or relation, uses :meth:`set_attributes` and +:meth:`set_relations` methods. -Remainder -~~~~~~~~~ - -Never, ever use the `entity.foo = 42` notation to update an entity. It -will not work. How to choose between a before and an after event ? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Before hooks give you access to the old attribute (or relation) -values. By definition the database is not yet updated in a before -hook. - -To access old and new values in an before_update_entity hook, one can -use the `server.hook.entity_oldnewvalue` function which returns a -tuple of the old and new values. This function takes an entity and an -attribute name as parameters. - -In a 'before_add|update_entity' hook the self.entity contains the new -values. One is allowed to further modify them before database -operations, using the dictionary notation. - -.. sourcecode:: python - - self.entity['age'] = 42 +'before_*' hooks give you access to the old attribute (or relation) +values. You can also hi-jack actually edited stuff in the case of entity +modification. Needing one of this will definitly guide your choice. -This is because using self.entity.set_attributes(age=42) will -immediately update the database (which does not make sense in a -pre-database hook), and will trigger any existing -before_add|update_entity hook, thus leading to infinite hook loops or -such awkward situations. - -Beyond these specific cases, updating an entity attribute or relation -must *always* be done using `set_attributes` and `set_relations` -methods. +Else the question is: should I need to do things before or after the actual +modification. If the answer is "it doesn't matter", use an 'after' event. -(Of course, ValidationError will always abort the current transaction, -whetever the event). -Peculiarities of inlined relations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some relations are defined in the schema as `inlined` (see -:ref:`RelationType` for details). In this case, they are inserted in -the database at the same time as entity attributes. - -Hence in the case of before_add_relation, such relations already exist -in the database. - -Edited attributes +Validation Errors ~~~~~~~~~~~~~~~~~ -On udpates, it is possible to ask the `entity.edited_attributes` -variable whether one attribute has been updated. +When a hook is responsible to maintain the consistency of the data model detect +an error, it must use a specific exception named +:exc:`~cubicweb.ValidationError`. Raising anything but a (subclass of) +:exc:`~cubicweb.ValidationError` is a programming error. Raising a it entails +aborting the current transaction. -.. sourcecode:: python +This exception is used to convey enough information up to the user +interface. Hence its constructor is different from the default Exception +constructor. It accepts, positionally: + +* an entity eid, - if 'age' not in entity.edited_attribute: - return +* a dict whose keys represent attribute (or relation) names and values + an end-user facing message (hence properly translated) relating the + problem. + + +Checking for object created/deleted in the current transaction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Deleted in transaction -~~~~~~~~~~~~~~~~~~~~~~ +In hooks, you can use the +:meth:`~cubicweb.server.session.Session.added_in_transaction` or +:meth:`~cubicweb.server.session.Session.deleted_in_transaction` of the session +object to check if an eid has been created or deleted during the hook's +transaction. -The session object has a deleted_in_transaction method, which can help -writing deletion Hooks. +This is useful to enable or disable some stuff if some entity is being added or +deleted. .. sourcecode:: python if self._cw.deleted_in_transaction(self.eidto): return -Given this predicate, we can avoid scheduling an operation. -Disabling hooks -~~~~~~~~~~~~~~~ - -It is sometimes convenient to disable some hooks. For instance to -avoid infinite Hook loops. One uses the `hooks_control` context -manager. - -This can be controlled more finely through the `category` Hook class -attribute, which is a string. +Peculiarities of inlined relations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. sourcecode:: python - - with hooks_control(self.session, self.session.HOOKS_ALLOW_ALL, ): - # ... do stuff - -.. autoclass:: cubicweb.server.session.hooks_control - -The existing categories are: ``email``, ``syncsession``, -``syncschema``, ``bookmark``, ``security``, ``worfklow``, -``metadata``, ``notification``, ``integrity``, ``activeintegrity``. - -Nothing precludes one to invent new categories and use the -hooks_control context manager to filter them (in or out). +Relations which are defined in the schema as `inlined` (see :ref:`RelationType` +for details) are inserted in the database at the same time as entity attributes. +This may have some side effect, for instance when creating entity and setting an +inlined relation in the same rql query, when 'before_add_relation' for that +relation will be run, the relation will already exist in the database (it's +usually not the case). diff -r 972bd504daf6 -r 308037210dab doc/book/en/devrepo/vreg.rst --- a/doc/book/en/devrepo/vreg.rst Mon Oct 18 11:04:19 2010 +0200 +++ b/doc/book/en/devrepo/vreg.rst Mon Oct 18 11:47:06 2010 +0200 @@ -38,6 +38,7 @@ .. autoclass:: cubicweb.selectors.match_kwargs .. autoclass:: cubicweb.selectors.appobject_selectable .. autoclass:: cubicweb.selectors.adaptable +.. autoclass:: cubicweb.selectors.configuration_values Result set selectors @@ -77,6 +78,7 @@ .. autoclass:: cubicweb.selectors.has_permission .. autoclass:: cubicweb.selectors.has_add_permission .. autoclass:: cubicweb.selectors.has_mimetype +.. autoclass:: cubicweb.selectors.is_in_state .. autoclass:: cubicweb.selectors.implements @@ -100,11 +102,13 @@ .. autoclass:: cubicweb.selectors.match_view .. autoclass:: cubicweb.selectors.primary_view .. autoclass:: cubicweb.selectors.specified_etype_implements +.. autoclass:: cubicweb.selectors.attribute_edited Other selectors ~~~~~~~~~~~~~~~ .. autoclass:: cubicweb.selectors.match_transition +.. autoclass:: cubicweb.selectors.debug_mode You'll also find some other (very) specific selectors hidden in other modules than :mod:`cubicweb.selectors`. diff -r 972bd504daf6 -r 308037210dab doc/book/en/intro/concepts.rst --- a/doc/book/en/intro/concepts.rst Mon Oct 18 11:04:19 2010 +0200 +++ b/doc/book/en/intro/concepts.rst Mon Oct 18 11:47:06 2010 +0200 @@ -32,7 +32,7 @@ On a Unix system, the available cubes are usually stored in the directory :file:`/usr/share/cubicweb/cubes`. If you're using the cubicweb forest -(:ref:SourceInstallation), the cubes are searched in the directory +(:ref:`SourceInstallation`), the cubes are searched in the directory :file:`/path/to/cubicweb_forest/cubes`. The environment variable :envvar:`CW_CUBES_PATH` gives additionnal locations where to search for cubes. diff -r 972bd504daf6 -r 308037210dab doc/book/en/makefile --- a/doc/book/en/makefile Mon Oct 18 11:04:19 2010 +0200 +++ b/doc/book/en/makefile Mon Oct 18 11:47:06 2010 +0200 @@ -10,7 +10,7 @@ SPHINXBUILD = sphinx-build PAPER = #BUILDDIR = build -BUILDDIR = ~/tmp/cwdoc +BUILDDIR = ../.. CWDIR = ../../.. JSDIR = ${CWDIR}/web/data JSTORST = ${CWDIR}/doc/tools/pyjsrest.py @@ -28,7 +28,6 @@ help: @echo "Please use \`make ' where is one of" @echo " all to make standalone HTML files, developer manual and API doc" - @echo " apidoc to make API doc" @echo " html to make standalone HTML files" @echo "--- " @echo " pickle to make pickle files (usable by e.g. sphinx-web)" @@ -38,21 +37,15 @@ @echo " linkcheck to check all external links for integrity" clean: - rm -rf apidoc/ rm -f *.html -rm -rf ${BUILDDIR}/* -rm -rf ${BUILDJS} -all: ${TARGET} apidoc html +all: ${TARGET} html %.html: %.txt ${MKHTML} ${MKHTMLOPTS} $< -#apydoc: -# epydoc --html -o epydoc/ -n ../server/*.py ../core/*.py ../common/*.py ../server/*/*.py ../modpython/*/*.py ../common/*/*.py -apidoc: - epydoc --html -o apidoc -n "cubicweb" --exclude=setup --exclude=__pkginfo__ ../../../ - # run sphinx ### html: js mkdir -p ${BUILDDIR}/html ${BUILDDIR}/doctrees diff -r 972bd504daf6 -r 308037210dab doc/book/fr/.static/cubicweb.png Binary file doc/book/fr/.static/cubicweb.png has changed diff -r 972bd504daf6 -r 308037210dab doc/book/fr/.static/logilab.png Binary file doc/book/fr/.static/logilab.png has changed diff -r 972bd504daf6 -r 308037210dab doc/book/fr/.static/sphinx-default.css --- a/doc/book/fr/.static/sphinx-default.css Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,860 +0,0 @@ -/** - * Sphinx Doc Design - */ - -html, body { - background: white; -} - -body { - font-family: Verdana, sans-serif; - font-size: 100%; - background-color: white; - color: black; - margin: 0; - padding: 0; -} - -/* :::: LAYOUT :::: */ - -div.logilablogo { - padding: 10px 10px 10px 10px; - height:75; -} - - -div.document { - background-color: white; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -div.body { - background-color: white; - padding: 0 20px 30px 20px; - border-left:solid; - border-left-color:#e2e2e2; - border-left-width:thin; -} - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; -} - -div.clearer { - clear: both; -} - -div.footer { - color: #ff4500; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ff4500; - text-decoration: underline; -} - -div.related { - background-color: #ff7700; - color: white; - width: 100%; - height: 30px; - line-height: 30px; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -div.related a { - color: white; - font-weight:bold; -} - -/* ::: TOC :::: */ - -div.sphinxsidebar { - border-style:solid; - border-color: white; -/* background-color:#e2e2e2;*/ - padding-bottom:5px; -} - -div.sphinxsidebar h3 { - font-family: 'Verdanda', sans-serif; - color: black; - font-size: 1.2em; - font-weight: normal; - margin: 0; - padding: 0; - font-weight:bold; - font-style:italic; -} - -div.sphinxsidebar h4 { - font-family: 'Verdana', sans-serif; - color: black; - font-size: 1.1em; - font-weight: normal; - margin: 5px 0 0 0; - padding: 0; - font-weight:bold; - font-style:italic; -} - -div.sphinxsidebar p { - color: black; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 10px; - padding: 0; - list-style: none; - color: black; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar a { - color: black; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #e2e2e2; - font-family: sans-serif; - font-size: 1em; - padding-bottom: 5px; -} - -/* :::: MODULE CLOUD :::: */ -div.modulecloud { - margin: -5px 10px 5px 10px; - padding: 10px; - line-height: 160%; - border: 1px solid #cbe7e5; - background-color: #f2fbfd; -} - -div.modulecloud a { - padding: 0 5px 0 5px; -} - -/* :::: SEARCH :::: */ -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* :::: COMMON FORM STYLES :::: */ - -div.actions { - padding: 5px 10px 5px 10px; - border-top: 1px solid #cbe7e5; - border-bottom: 1px solid #cbe7e5; - background-color: #e0f6f4; -} - -form dl { - color: #333; -} - -form dt { - clear: both; - float: left; - min-width: 110px; - margin-right: 10px; - padding-top: 2px; -} - -input#homepage { - display: none; -} - -div.error { - margin: 5px 20px 0 0; - padding: 5px; - border: 1px solid #d00; - font-weight: bold; -} - -/* :::: INLINE COMMENTS :::: */ - -div.inlinecomments { - position: absolute; - right: 20px; -} - -div.inlinecomments a.bubble { - display: block; - float: right; - background-image: url(style/comment.png); - background-repeat: no-repeat; - width: 25px; - height: 25px; - text-align: center; - padding-top: 3px; - font-size: 0.9em; - line-height: 14px; - font-weight: bold; - color: black; -} - -div.inlinecomments a.bubble span { - display: none; -} - -div.inlinecomments a.emptybubble { - background-image: url(style/nocomment.png); -} - -div.inlinecomments a.bubble:hover { - background-image: url(style/hovercomment.png); - text-decoration: none; - color: #3ca0a4; -} - -div.inlinecomments div.comments { - float: right; - margin: 25px 5px 0 0; - max-width: 50em; - min-width: 30em; - border: 1px solid #2eabb0; - background-color: #f2fbfd; - z-index: 150; -} - -div#comments { - border: 1px solid #2eabb0; - margin-top: 20px; -} - -div#comments div.nocomments { - padding: 10px; - font-weight: bold; -} - -div.inlinecomments div.comments h3, -div#comments h3 { - margin: 0; - padding: 0; - background-color: #2eabb0; - color: white; - border: none; - padding: 3px; -} - -div.inlinecomments div.comments div.actions { - padding: 4px; - margin: 0; - border-top: none; -} - -div#comments div.comment { - margin: 10px; - border: 1px solid #2eabb0; -} - -div.inlinecomments div.comment h4, -div.commentwindow div.comment h4, -div#comments div.comment h4 { - margin: 10px 0 0 0; - background-color: #2eabb0; - color: white; - border: none; - padding: 1px 4px 1px 4px; -} - -div#comments div.comment h4 { - margin: 0; -} - -div#comments div.comment h4 a { - color: #d5f4f4; -} - -div.inlinecomments div.comment div.text, -div.commentwindow div.comment div.text, -div#comments div.comment div.text { - margin: -5px 0 -5px 0; - padding: 0 10px 0 10px; -} - -div.inlinecomments div.comment div.meta, -div.commentwindow div.comment div.meta, -div#comments div.comment div.meta { - text-align: right; - padding: 2px 10px 2px 0; - font-size: 95%; - color: #538893; - border-top: 1px solid #cbe7e5; - background-color: #e0f6f4; -} - -div.commentwindow { - position: absolute; - width: 500px; - border: 1px solid #cbe7e5; - background-color: #f2fbfd; - display: none; - z-index: 130; -} - -div.commentwindow h3 { - margin: 0; - background-color: #2eabb0; - color: white; - border: none; - padding: 5px; - font-size: 1.5em; - cursor: pointer; -} - -div.commentwindow div.actions { - margin: 10px -10px 0 -10px; - padding: 4px 10px 4px 10px; - color: #538893; -} - -div.commentwindow div.actions input { - border: 1px solid #2eabb0; - background-color: white; - color: #135355; - cursor: pointer; -} - -div.commentwindow div.form { - padding: 0 10px 0 10px; -} - -div.commentwindow div.form input, -div.commentwindow div.form textarea { - border: 1px solid #3c9ea2; - background-color: white; - color: black; -} - -div.commentwindow div.error { - margin: 10px 5px 10px 5px; - background-color: #fbe5dc; - display: none; -} - -div.commentwindow div.form textarea { - width: 99%; -} - -div.commentwindow div.preview { - margin: 10px 0 10px 0; - background-color: #70d0d4; - padding: 0 1px 1px 25px; -} - -div.commentwindow div.preview h4 { - margin: 0 0 -5px -20px; - padding: 4px 0 0 4px; - color: white; - font-size: 1.3em; -} - -div.commentwindow div.preview div.comment { - background-color: #f2fbfd; -} - -div.commentwindow div.preview div.comment h4 { - margin: 10px 0 0 0!important; - padding: 1px 4px 1px 4px!important; - font-size: 1.2em; -} - -/* :::: SUGGEST CHANGES :::: */ -div#suggest-changes-box input, div#suggest-changes-box textarea { - border: 1px solid #ccc; - background-color: white; - color: black; -} - -div#suggest-changes-box textarea { - width: 99%; - height: 400px; -} - - -/* :::: PREVIEW :::: */ -div.preview { - background-image: url(style/preview.png); - padding: 0 20px 20px 20px; - margin-bottom: 30px; -} - - -/* :::: INDEX PAGE :::: */ - -table.contentstable { - width: 90%; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* :::: INDEX STYLES :::: */ - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable dl, table.indextable dd { - margin-top: 0; - margin-bottom: 0; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -form.pfform { - margin: 10px 0 20px 0; -} - -/* :::: GLOBAL STYLES :::: */ - -.docwarning { - background-color: #ffe4e4; - padding: 10px; - margin: 0 -20px 0 -20px; - border-bottom: 1px solid #f66; -} - -p.subhead { - font-weight: bold; - margin-top: 20px; -} - -a { - color: black; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Verdana', sans-serif; - background-color: white; - font-weight: bold; - color: black; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 150%; } -div.body h2 { font-size: 120%; } -div.body h3 { font-size: 100%; } -div.body h4 { font-size: 80%; } -div.body h5 { font-size: 600%; } -div.body h6 { font-size: 40%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - text-align: justify; - line-height: 130%; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -ul.fakelist { - list-style: none; - margin: 10px 0 10px 20px; - padding: 0; -} - -.field-list ul { - padding-left: 1em; -} - -.first { - margin-top: 0 !important; -} - -/* "Footnotes" heading */ -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -/* "Topics" */ - -div.topic { - background-color: #eee; - border: 1px solid #ccc; - padding: 0 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* Admonitions */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -div.admonition p { - display: inline; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -table.docutils { - border: 0; -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 0; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.field-list td, table.field-list th { - border: 0 !important; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -dl { - margin-bottom: 15px; - clear: both; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -.refcount { - color: #060; -} - -dt:target, -.highlight { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -th { - text-align: left; - padding-right: 5px; -} - -pre { - padding: 5px; - background-color: #efc; - color: #333; - border: 1px solid #ac9; - border-left: none; - border-right: none; - overflow: auto; -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -tt { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - font-size: 0.95em; -} - -tt.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -tt.descclassname { - background-color: transparent; -} - -tt.xref, a tt { - background-color: transparent; - font-weight: bold; -} - -.footnote:target { background-color: #ffa } - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - background-color: transparent; -} - -.optional { - font-size: 1.3em; -} - -.versionmodified { - font-style: italic; -} - -form.comment { - margin: 0; - padding: 10px 30px 10px 30px; - background-color: #eee; -} - -form.comment h3 { - background-color: #326591; - color: white; - margin: -10px -30px 10px -30px; - padding: 5px; - font-size: 1.4em; -} - -form.comment input, -form.comment textarea { - border: 1px solid #ccc; - padding: 2px; - font-family: sans-serif; - font-size: 100%; -} - -form.comment input[type="text"] { - width: 240px; -} - -form.comment textarea { - width: 100%; - height: 200px; - margin-bottom: 10px; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -/* :::: PRINT :::: */ -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0; - width : 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - div#comments div.new-comment-box, - #top-link { - display: none; - } -} diff -r 972bd504daf6 -r 308037210dab doc/book/fr/.templates/layout.html --- a/doc/book/fr/.templates/layout.html Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +0,0 @@ -{%- block doctype -%} - -{%- endblock %} -{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} -{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} -{%- macro relbar %} - -{%- endmacro %} -{%- macro sidebar %} - {%- if builder != 'htmlhelp' %} -
-
- {%- block sidebarlogo %} - {%- if logo %} - - {%- endif %} - {%- endblock %} - {%- block sidebartoc %} - {%- if display_toc %} -

Table Of Contents

- {{ toc }} - {%- endif %} - {%- endblock %} - {%- block sidebarrel %} - {%- if prev %} -

Previous topic

-

{{ prev.title }}

- {%- endif %} - {%- if next %} -

Next topic

-

{{ next.title }}

- {%- endif %} - {%- endblock %} - {%- if sourcename %} -

This Page

- - {%- endif %} - {%- if customsidebar %} - {{ rendertemplate(customsidebar) }} - {%- endif %} - {%- block sidebarsearch %} - {%- if pagename != "search" %} -

{{ builder == 'web' and 'Keyword' or 'Quick' }} search

- - {%- if builder == 'web' %} -

Enter a module, class or function name.

- {%- endif %} - {%- endif %} - {%- endblock %} -
-
- {%- endif %} -{%- endmacro -%} - - - - - {%- if builder != 'htmlhelp' %} - {%- set titlesuffix = " — " + docstitle %} - {%- endif %} - {{ title|striptags }}{{ titlesuffix }} - {%- if builder == 'web' %} - - {%- for link, type, title in page_links %} - - {%- endfor %} - {%- else %} - - - {%- endif %} - {%- if builder != 'htmlhelp' %} - - - - - {%- if use_opensearch %} - - {%- endif %} - {%- if favicon %} - - {%- endif %} - {%- endif %} -{%- block rellinks %} - {%- if hasdoc('about') %} - - {%- endif %} - - - - {%- if hasdoc('copyright') %} - - {%- endif %} - - {%- if parents %} - - {%- endif %} - {%- if next %} - - {%- endif %} - {%- if prev %} - - {%- endif %} -{%- endblock %} -{%- block extrahead %}{% endblock %} - - - -{% block logilablogo %} - -{% endblock %} - -{%- block relbar1 %}{{ relbar() }}{% endblock %} - -{%- block sidebar1 %}{# possible location for sidebar #}{% endblock %} - -{%- block document %} -
-
- {%- if builder != 'htmlhelp' %} -
- {%- endif %} -
- {% block body %}{% endblock %} -
- {%- if builder != 'htmlhelp' %} -
- {%- endif %} -
-{%- endblock %} - -{%- block sidebar2 %}{{ sidebar() }}{% endblock %} -
-
- -{%- block relbar2 %}{{ relbar() }}{% endblock %} - -{%- block footer %} - -{%- endblock %} - - diff -r 972bd504daf6 -r 308037210dab doc/book/fr/01-introduction.fr.txt --- a/doc/book/fr/01-introduction.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,343 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _Overview: - -Aperçu rapide de `CubicWeb` -=========================== - -`CubicWeb` nous permet de développer des instances d'applications web -basées sur un ou plusieurs `cube`. - -Ce à quoi nous réferrons en parlant de `cube` est un modèle définissant -des types de données et des vues. Un `cube` est un composant re-utilisable -regroupé avec d'autres cubes sur le système de fichiers. - -Un `instance` réferre à une installation spécifique d'un ou plusieurs cubes -où sont regroupés tous les fichiers de configuration de l'application web finale. - -Dans ce document, nous allons vous montrer comment créer un cube et l'utiliser -dans une instance pour votre application web. - -Créez votre cube ----------------- - -Après avoir installé votre environement de développement `CubicWeb`, vous pouvez -commencer à construire votre premier cube: :: - - cubicweb-ctl newcube blog - -Cela va créer dans ``/path/to/forest/cubes`` une répertoire contenant :: - - blog/ - | - |-- data/ - | |-- cubes.blog.css - | |-- cubes.blog.js - | |-- external_resources - | - |-- debian/ - | |-- changelog - | |-- compat - | |-- control - | |-- copyright - | |-- cubicweb-blog.prerm - | |-- rules - | - |-- entities.py - | - |-- i18n/ - | |-- en.po - | |-- fr.po - | - |-- __init__.py - | - |-- MANIFEST.in - | - |-- migration/ - | |-- postcreate.py - | |-- precreate.py - | - |-- __pkginfo__.py - | - |-- schema.py - | - |-- setup.py - | - |-- site_cubicweb.py - | - |-- sobjects.py - | - |-- test/ - | |-- data/ - | |-- bootstrap_cubes - | |-- pytestconf.py - | |-- realdb_test_blog.py - | |-- test_blog.py - | - |-- views.py - -Toute modification apportée à votre modele de données devra -etre effectué dans ce répertoire. - - - -Définissez votre schéma de données ----------------------------------- - -Le modèle de données ou schéma est au coeur d'une application `CubicWeb`. -C'est là où vous allez devoir définir le type de contenu que votre application -devra gérer. - -Votre modele de données est défini dans le fichier ``schema.py`` de votre cube -``blog`` comme suit. - -:: - - from cubicweb.schema import format_constraint - class Blog(EntityType): - title = String(maxsize=50, required=True) - description = String() - - class BlogEntry(EntityType): - title = String(required=True, fulltextindexed=True, maxsize=256) - publish_date = Date(default='TODAY') - content = String(required=True, fulltextindexed=True) - entry_of = SubjectRelation('Blog', cardinality='?*') - -Un ``Blog`` a un titre et une description. Le titre est une chaîne -de caractères requise par la classe parente EntityType et ne doit -pas excéder 50 caractères. La description est une chaîne de -caractères sans contraintes. - -Une ``BlogEntry`` a un titre, une date de publication et du texte -étant son contenu. Le titre est une chaîne de caractères qui ne -doit pas excéder 100 caractères. La date de publication est de type Date et a -pour valeur par défaut TODAY, ce qui signifie que lorsqu'une -``BlogEntry`` sera créée, sa date de publication sera la date -courante a moins de modifier ce champ. Le texte est une chaîne de -caractères qui sera indexée en plein texte et sans contraintes. - -Une ``BlogEntry`` a aussi une relation nommée ``entry_of`` qui la -relie à un ``Blog``. La cardinalité ``?*`` signifie que BlogEntry -peut faire partie de zero a un Blog (``?`` signifie `zero ou un`) et -qu'un Blog peut avoir une infinité de BlogEntry (``*`` signifie -`n'importe quel nombre incluant zero`). -Par soucis de complétude, nous rappellerons que ``+`` signifie -`un ou plus`. - - -Créez votre instance --------------------- - -:: - - cubicweb-ctl create blog blogdemo - -Cette commande va créer un répertoire ``~/etc/cubicweb.d/blogdemo`` -contenant tous les fichiers de configuration nécessaire au lancement -de votre application web. - -L'instance ``blogdemo`` est construite sur le cube ``blog``. - -Bienvenue dans votre application web ------------------------------------- - -Lancez votre application en exécutant : :: - - cubicweb-ctl start -D blogdemo - - -Vous pouvez à présent accéder à votre application web vous permettant de -créer des blogs et d'y poster des messages en visitant l'URL http://localhost:8080/. -Un premier formulaire d'authentification va vous être proposé. Par défaut, -l'application n'autorisera pas d'utilisateur anonyme à accéder a votre -application. Vous devrez donc utiliser l'utilisateur administrateur que -vous aurez crée lors de l'initialisation de votre base de données via -``cubicweb-ctl create``. - -.. image:: images/login-form.png - - -Une fois authentifié, vous pouvez commencer à jouer avec votre -application et créer des entités. Bravo ! - -.. image:: images/blog-demo-first-page.png - - -Rappelez-vous que pour le moment, tout a été géré par la plate-forme -`CubicWeb` et que la seule chose qui a été fournie est le schéma de -données. - -Créons des entités ------------------- - -Nous allons maintenant créer quelques entités dans notre application. - -Créez un Blog -~~~~~~~~~~~~~ - -Créons à présent quelques entités. Cliquez sur `[+]` sur la -droite du lien Blog. Appelez cette nouvelle entité Blog ``Tech-Blog`` -et tapez pour la description ``everything about technology``, -puis validez le formulaire d'édition en cliquant sur le bouton -``Validate``. - -.. image:: images/cbw-create-blog.fr.png - :alt: from to create blog - -En cliquant sur le logo situé dans le coin gauche de la fenêtre, -vous allez être redirigé vers la page d'accueil. Ensuite, si vous allez -sur le lien Blog, vous devriez voir la liste des entités Blog, en particulier -celui que vous venez juste de créer ``Tech-Blog``. - -.. image:: images/cbw-list-one-blog.fr.png - :alt: displaying a list of a single blog - -Si vous cliquez sur ``Tech-Blog`` vous devriez obtenir une description -détaillée, ce qui dans notre cas, n'est rien de plus que le titre -et la phrase ``everything about technology`` - -Maintenant retournons sur la page d'accueil et créons un nouveau -Blog ``MyLife`` et retournons sur la page d'accueil, puis suivons -le lien Blog et nous constatons qu'à présent deux blogs sont listés. - -.. image:: images/cbw-list-two-blog.fr.png - :alt: displaying a list of two blogs - -Créons un article -~~~~~~~~~~~~~~~~~ - -Revenons sur la page d'accueil et cliquons sur `[+]` à droite du lien -`articles`. Appellons cette nouvelle entité ``Hello World`` et introduisons -un peut de texte avant de ``Valider``. Vous venez d'ajouter un article -sans avoir précisé à quel Blog il appartenait. Dans la colonne de gauche -se trouve une boite intitulé ``actions``, cliquez sur le menu ``modifier``. -Vous êtes de retour sur le formulaire d'édition de l'article que vous -venez de créer, à ceci près que ce formulaire a maintenant une nouvelle -section intitulée ``ajouter relation``. Choisissez ``entry_of`` dans ce menu, -cela va faire apparaitre une deuxième menu déroulant dans lequel vous -allez pouvoir séléctionner le Blog ``MyLife``. - -Vous auriez pu aussi, au moment où vous avez crée votre article, sélectionner -``appliquer`` au lieu de ``valider`` et le menu ``ajouter relation`` serait apparu. - -.. image:: images/cbw-add-relation-entryof.fr.png - :alt: editing a blog entry to add a relation to a blog - -Validez vos modifications en cliquant sur ``Valider``. L'entité article -qui est listée contient maintenant un lien vers le Blog auquel il -appartient, ``MyLife``. - -.. image:: images/cbw-detail-one-blogentry.fr.png - :alt: displaying the detailed view of a blogentry - -Rappelez-vous que pour le moment, tout a été géré par la plate-forme -`CubicWeb` et que la seule chose qui a été fournie est le schéma de -données. D'ailleurs pour obtenir une vue graphique du schéma, visitez -le lien `Application schema`` a l'URL suivante : -http://localhost:8080/view?vid=schema - -.. image:: images/cbw-schema.fr.png - :alt: graphical view of the schema (aka data-model) - - -Définissez les vues de vos données ----------------------------------- - -Le principe de sélection des vues -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Une vue est une classe Python qui inclut: - - - un identifiant (tous les objets dans `CubicWeb` sont listés - dans un registre et cet identifiant est utilisé comme la clé) - - - un filtre de sélection de `result sets` auxquels il - peut s'appliquer - -`CubicWeb` fournit un ensemble de vues standard pour le type d'objet -`EntityView`. vous poubez les trouver listées dans ``cubicweb/web/views``. - -Une vue est appliquée sur un `result set` qui représente l'ensemble -des entités que nous cherchons à appliquer. `CubicWeb` utilise un -sélecteur qui permet de calculer un score et d'identifier la vue -la plus adaptée au `result set` que nous voulons afficher. La librarie -standard des sélecteurs se trouve dans ``cubicweb.common.selector`` -et une librairie des méthodes utilisées pour calculer les scores -est dans ``cubicweb.vregistry.vreq``. - - -Il est possible de définir plusieurs vues ayant le meme identifiant -et d'y associer des sélecteurs et des filtres afin de permettre à -l'application de s'adapter au mieux aux données que nous avons -besoin d'afficher. Nous verrons cela plus en détails dans :ref:`DefinitionVues`. - -On peut citer l'exemple de la vue nommée ``primary`` qui est celle utilisée -pour visualiser une entité seule. Nous allons vous montrer comment modifier -cette vue. - -Modification des vues -~~~~~~~~~~~~~~~~~~~~~ -Si vous souhaitez modifier la manière dont est rendue un article (`Blogentry`), -vous devez surcharger la vue ``primary`` définie dans le module ``views`` de -votre cube, ``cubes/blog/views.py``. - -Nous pourrions par exemple ajouter devant la date de publication un préfixe -indiquant que la date visualisée est la date de publication. - -Pour cela appliquez les modifications suivantes: - -:: - - from cubicweb.web.views import baseviews - - - class BlogEntryPrimaryView(baseviews.PrimaryView): - - accepts = ('BlogEntry',) - - def render_entity_title(self, entity): - self.w(u'

%s

' % html_escape(entity.dc_title())) - - def content_format(self, entity): - return entity.view('reledit', rtype='content_format') - - def cell_call(self, row, col): - entity = self.rset.get_entity(row, col) - - # display entity attributes with prefixes - self.w(u'

%s

' % entity.title) - self.w(u'

published on %s

' % entity.publish_date.strftime('%Y-%m-%d')) - self.w(u'

%s

' % entity.content) - - # display relations - siderelations = [] - if self.main_related_section: - self.render_entity_relations(entity, siderelations) - -.. note:: - Lors qu'une vue est modifiée il n'est pas nécessaire de relancer - l'application. Sauvez juste le fichier Python et rechargez la page - dans votre navigateur afin de visualiser les modifications. - - -Nous pouvons voir que la date de publication est préfixée comme souhaitée. - - -.. image:: images/cbw-update-primary-view.fr.png - :alt: modified primary view - - - -Le code que nous avons modifié définit une vue primaire pour une entité de -type `BlogEntry`. - -Etant donné que les vues sont appliquées sur des `result sets` et que -les `result sets` peuvent être des tableaux de données, il est indispensable -de récupérer l'entité selon ses coordonnées (row,col). - -La méthode ``self.w()`` est utilisée pour afficher des données. En particulier -dans notre exemple, nous l'utilisons pour afficher des tags HTML et des valeurs -des attributs de notre entité. - - diff -r 972bd504daf6 -r 308037210dab doc/book/fr/02-foundation.fr.txt --- a/doc/book/fr/02-foundation.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,431 +0,0 @@ -.. -*- coding: utf-8 -*- - -Fondements `CubicWeb` -===================== -Un peu d'histoire... --------------------- - -`CubicWeb` est une plate-forme logicielle de développement d'application web -qui est développée par Logilab_ depuis 2001. - - -Entièrement développée en Python, `CubicWeb` publie des données provenant -de plusieurs sources telles que des bases de données SQL, des répertoire -LDAP et des systèmes de gestion de versions tels que subversion. - -L'interface utilisateur de `CubicWeb` a été spécialement conçue pour laisser -à l'utilisateur final toute latitude pour sélectionner puis présenter les données. -Elle permet d'explorer aisément la base de connaissances et d'afficher les -résultats avec la présentation la mieux adaptée à la tâche en cours. -La flexibilité de cette interface redonne à l'utilisateur le contrôle de -paramètres d'affichage et de présentation qui sont habituellement réservés -aux développeurs. - -Parmi les applications déjà réalisées, on dénombre un annuaire en ligne pour -le grand public (voir http://www.118000.fr/), un système de gestion d'études -numériques et de simulations pour un bureau d'études, un service de garde -partagée d'enfants (voir http://garde-partagee.atoukontact.fr/), la gestion -du développement de projets logiciels (voir http://www.logilab.org), etc. - -En 2008, `CubicWeb` a été porté pour un nouveau type de source: le datastore -de GoogleAppEngine_. - -.. _GoogleAppEngine: http://code.google.com/appengine/ - - -Architecture globale --------------------- -.. image:: images/archi_globale.png - -**Note**: en pratique la partie cliente et la partie serveur sont -généralement intégrées dans le même processus et communiquent donc -directement, sans nécessiter des appels distants via Pyro. Il est -cependant important de retenir que ces deux parties sont disjointes -et qu'il est même possible d'en exécuter plusieurs exemplaires dans -des processus distincts pour répartir la charge globale d'un site -sur une ou plusieurs machines. - -Concepts et vocabulaire ------------------------ - -*schéma* - le schéma définit le modèle de données d'une application sous forme - d'entités et de relations, grâce à la bibliothèque `yams`_. C'est - l'élément central d'une application. Il est initialement défini sur - le système de fichiers et est stocké dans la base de données lors de - la création d'une instance. `CubicWeb` fournit un certain nombres de - types d'entités inclus systématiquement car nécessaire au noyau - `CubicWeb` et une librairie de cubes devant être inclus - explicitement le cas échéant. - -*type d'entité* - une entité est un ensemble d'attributs ; l'attribut de - base de toute entité, qui est sa clef, est l'eid - -*type de relation* - les entités sont liées entre elles par des relations. Dans `CubicWeb` - les relations sont binaires : par convention on nomme le premier terme - d'une relation son 'sujet' et le second son 'objet'. - -*type d'entité final* - les types finaux correspondent aux types de bases comme les chaînes - de caractères, les nombres entiers... Une propriété de ces types est - qu'ils ne peuvent être utilisés qu'uniquement comme objet d'une - relation. Les attributs d'une entité (non finale) sont des entités - (finales). - -*type de relation finale* - une relation est dite finale si son objet est un type final. Cela revient à - un attribut d'une entité. - -*entrepôt* - ou *repository*, c'est la partie serveur RQL de `CubicWeb`. Attention à ne pas - confondre avec un entrepôt mercurial ou encore un entrepôt debian. - -*source* - une source de données est un conteneur de données quelquonque (SGBD, annuaire - LDAP...) intégré par l'entrepôt `CubicWeb`. Un entrepôt possède au moins une source - dite "system" contenant le schéma de l'application, l'index plein-texte et - d'autres informations vitales au système. - -*configuration* - il est possible de créer différentes configurations pour une instance : - - - ``repository`` : entrepôt uniquement, accessible pour les clients via Pyro - - ``twisted`` : interface web uniquement, communiquant avec un entrepôt via Pyro - - ``all-in-one`` : interface web et entrepôt dans un seul processus. L'entrepôt - peut ou non être accessible via Pyro - -*cube* - un cube est un modèle regroupant un ou plusieurs types de données et/ou - des vues afin de fournir une fonctionalité précise, ou une application `CubicWeb` - complète utilisant éventuellement d'autres cube. Les différents - cubes disponibles sur une machine sont installés dans - `/path/to/forest/cubicweb/cubes` - -*instance* - une instance est une installation spécifique d'un cube. Sont regroupes - dans une instance tous les fichiers de configurations necessaires au bon - fonctionnement de votre application web. Elle referrera au(x) cube(s) sur - le(s)quel(s) votre application se base. - Par exemple intranet/jpl et logilab.org sont deux instances du cube jpl que - nous avons developpes en interne. - Les instances sont définies dans le répertoire `~/etc/cubicweb.d`. - -*application* - le mot application est utilisé parfois pour parler d'une instance et parfois - d'un composant, en fonction du contexte... Mieux vaut donc éviter de - l'utiliser et utiliser plutôt *cube* et *instance*. - -*result set* - objet encaspulant les résultats d'une requête RQL et des informations sur - cette requête. - -*Pyro* - `Python Remote Object`_, système d'objets distribués pur Python similaire à - Java's RMI (Remote Method Invocation), pouvant être utilisé pour la - communication entre la partie web du framework et l'entrepôt RQL. - -.. _`Python Remote Object`: http://pyro.sourceforge.net/ -.. _`yams`: http://www.logilab.org/project/name/yams/ - - -Moteur `CubicWeb` ------------------ - -Le moteur web de `CubicWeb` consiste en quelques classes gérant un ensemble d'objets -chargés dynamiquement au lancement de `CubicWeb`. Ce sont ces objets dynamiques, issus -du modèle ou de la librairie, qui construisent le site web final. Les différents -composants dynamiques sont par exemple : - -* coté client et serveur - - - les définitions d'entités, contenant la logique permettant la manipulation des - données de l'application - -* coté client - - - les *vues* , ou encore plus spécifiquement - - - les boites - - l'en-tête et le pied de page - - les formulaires - - les gabarits de pages - - - les *actions* - - les *controleurs* - -* coté serveur - - - les crochets de notification - - les vues de notification - -Les différents composants du moteur sont : - -* un frontal web (seul twisted disponible pour le moment), transparent du point - de vue des objets dynamiques -* un objet encapsulant la configuration -* un `vregistry` (`cubicweb.cwvreg`) contenant les objets chargés dynamiquements - -Détail de la procédure d'enregistrement ---------------------------------------- -Au démarage le `vregistry` ou base de registres inspecte un certain nombre de -répertoires à la recherche de définition de classes "compatible". Après une -procédure d'enregistrement les objets sont affectés dans différents registres -afin d'être ensuite séléctionné dynamiquement pendant le fonctionnement de -l'application. - -La classe de base de tout ces objets est la classe `AppRsetObject` (module -`cubicweb.common.appobject`). - - -API Python/RQL --------------- - -Inspiré de la db-api standard, avec un object Connection possédant les méthodes -cursor, rollback et commit principalement. La méthode importante est la méthode -`execute` du curseur : - -`execute(rqlstring, args=None, eid_key=None, build_descr=True)` - -:rqlstring: la requête rql à éxécuter (unicode) -:args: si la requête contient des substitutions, un dictionnaire contenant les - valeurs à utiliser -:eid_key: - un détail d'implémentation du cache de requêtes RQL fait que si une substitution est - utilisée pour introduire un eid *levant des ambiguités dans la résolution de - type de la requête*, il faut spécifier par cet argument la clé correspondante - dans le dictionnaire - -C'est l'objet Connection qui possède les méthodes classiques `commit` et -`rollback`. Vous ne *devriez jamais avoir à les utiliser* lors du développement -d'interface web sur la base du framework `CubicWeb` étant donné que la fin de la -transaction est déterminée par celui-ci en fonction du succès d'éxécution de la -requête. - -NOTE : lors de l'éxécution de requêtes de modification (SET,INSERT,DELETE), si une -requête génère une erreur liée à la sécurité, un rollback est systématiquement -effectuée sur la transaction courante. - - -La classe `Request` (`cubicweb.web`) ------------------------------------- -Une instance de requête est créée lorsque une requête HTTP est transmise au -serveur web. Elle contient des informations telles que les paramètres de -formulaires, l'utilisateur connecté, etc. - -**De manière plus générale une requête représente une demande d'un utilisateur, -que se soit par HTTP ou non (on parle également de requête rql coté serveur par -exemple)** - -Une instance de la classe `Request` possède les attributs : - -* `user`, instance de`cubicweb.common.utils.User` correspondant à l'utilisateur - connecté -* `form`, dictionaire contenant les valeurs de formulaire web -* `encoding`, l'encodage de caractère à utiliser dans la réponse - -Mais encore : - -:Gestion des données de session: - * `session_data()`, retourne un dictionaire contenant l'intégralité des - données de la session - * `get_session_data(key, default=None)`, retourne la valeur associée à - la clé ou la valeur `default` si la clé n'est pas définie - * `set_session_data(key, value)`, associe une valeur à une clé - * `del_session_data(key)`, supprime la valeur associé à une clé - - -:Gestion de cookie: - * `get_cookie()`, retourne un dictionnaire contenant la valeur de l'entête - HTTP 'Cookie' - * `set_cookie(cookie, key, maxage=300)`, ajoute un en-tête HTTP `Set-Cookie`, - avec une durée de vie 5 minutes par défault (`maxage` = None donne un cooke - *de session"* expirant quand l'utilisateur ferme son navigateur - * `remove_cookie(cookie, key)`, fait expirer une valeur - -:Gestion d'URL: - * `url()`, retourne l'url complète de la requête HTTP - * `base_url()`, retourne l'url de la racine de l'application - * `relative_path()`, retourne chemin relatif de la requête - -:Et encore...: - * `set_content_type(content_type, filename=None)`, place l'en-tête HTTP - 'Content-Type' - * `get_header(header)`, retourne la valeur associé à un en-tête HTTP - arbitraire de la requête - * `set_header(header, value)`, ajoute un en-tête HTTP arbitraire dans la - réponse - * `cursor()` retourne un curseur RQL sur la session - * `execute(*args, **kwargs)`, raccourci vers .cursor().execute() - * `property_value(key)`, gestion des propriétés (`CWProperty`) - * le dictionaire `data` pour stocker des données pour partager de - l'information entre les composants *durant l'éxécution de la requête*. - -A noter que cette classe est en réalité abstraite et qu'une implémentation -concrète sera fournie par le *frontend* web utilisé (en l'occurent *twisted* -aujourd'hui). Enfin pour les vues ou autres qui sont éxécutés coté serveur, -la majeure partie de l'interface de `Request` est définie sur la session -associée au client. - - -La classe `AppObject` ---------------------- - -En général : - -* on n'hérite pas directement des cette classe mais plutôt d'une classe - plus spécifique comme par exemple `AnyEntity`, `EntityView`, `AnyRsetView`, - `Action`... - -* pour être enregistrable, un classe fille doit définir son registre (attribut - `__registry__`) et son identifiant (attribut `id`). Généralement on n'a pas à - s'occuper du registre, uniquement de l'identifiant `id` :) - -On trouve un certain nombre d'attributs et de méthodes définis dans cette classe -et donc commune à tous les objets de l'application : - -A l'enregistrement, les attributs suivants sont ajoutés dynamiquement aux -*classes* filles: - -* `vreg`, le `vregistry` de l'application -* `schema`, le schéma de l'application -* `config`, la configuration de l'application - -On trouve également sur les instances les attributs : - -* `req`, instance de `Request` -* `rset`, le "result set" associé à l'objet le cas échéant -* `cursor`, curseur rql sur la session - - -:Gestion d'URL: - * `build_url(method=None, **kwargs)`, retourne une URL absolue construites à - partir des arguments donnés. Le *controleur* devant gérer la réponse - peut-être spécifié via l'argument spécial `method` (le branchement est - théoriquement bien effectué automatiquement :). - - * `datadir_url()`, retourne l'url du répertoire de données de l'application - (contenant les fichiers statiques tels que les images, css, js...) - - * `base_url()`, raccourci sur `req.base_url()` - - * `url_quote(value)`, version *unicode safe* de de la fonction `urllib.quote` - -:Manipulation de données: - - * `etype_rset(etype, size=1)`, raccourci vers `vreg.etype_rset()` - - * `eid_rset(eid, rql=None, descr=True)`, retourne un objet result set pour - l'eid donné - * `entity(row, col=0)`, retourne l'entité correspondant à la position données - du "result set" associé à l'objet - - * `complete_entity(row, col=0, skip_bytes=True)`, équivalent à `entity` mais - appelle également la méthode `complete()` sur l'entité avant de la retourner - -:Formattage de données: - * `format_date(date, date_format=None, time=False)` - * `format_time(time)`, - -:Et encore...: - - * `external_resource(rid, default=_MARKER)`, accède à une valeur définie dans - le fichier de configuration `external_resource` - - * `tal_render(template, variables)`, - - -**NOTE IMPORTANTE** -Lorsqu'on hérite d'`AppObject` (même indirectement), il faut **toujours** -utiliser **super()** pour récupérer les méthodes et attributs des classes -parentes, et pas passer par l'identifiant de classe parente directement. -(sous peine de tomber sur des bugs bizarres lors du rechargement automatique -des vues). Par exemple, plutôt que d'écrire:: - - class Truc(PrimaryView): - def f(self, arg1): - PrimaryView.f(self, arg1) - -Il faut écrire:: - - class Truc(PrimaryView): - def f(self, arg1): - super(Truc, self).f(arg1) - - - - - - -Structure standard d'un cube ----------------------------- - -Un cube complexe est structuré selon le modèle suivant : - -:: - - moncube/ - | - |-- schema.py - | - |-- entities/ - | - |-- sobjects/ - | - |-- views/ - | - |-- test/ - | - |-- i18n/ - | - |-- data/ - | - |-- migration/ - | |- postcreate.py - | \- depends.map - | - |-- debian/ - | - \-- __pkginfo__.py - -On peut utiliser de simple module python plutôt que des répertoires (packages), -par ex.: - -:: - - moncube/ - | - |-- entities.py - |-- hooks.py - \-- views.py - - -où : - -* ``schema`` contient la définition du schéma (coté serveur uniquement) -* ``entities`` contient les définitions d'entités (coté serveur et interface web) -* ``sobjects`` contient les crochets et/ou vues de notification (coté serveur - uniquement) -* ``views`` contient les différents composants de l'interface web (coté interface - web uniquement) -* ``test`` contient les tests spécifiques à l'application (non installé) -* ``i18n`` contient les catalogues de messages pour les langues supportées (coté - serveur et interface web) -* ``data`` contient des fichiers de données arbitraires servis statiquement - (images, css, fichiers javascripts)... (coté interface web uniquement) -* ``migration`` contient le fichier d'initialisation de nouvelles instances - (``postcreate.py``) et générallement un fichier donnant les dépendances `CubicWeb` du - composant en fonction de la version de celui-ci (``depends.map``) -* ``debian`` contient les fichiers contrôlant le packaging debian (vous y - trouverez les fichiers classiques ``control``, ``rules``, ``changelog``... (non - installé) -* le fichier ``__pkginfo__.py`` donne un certain nombre de méta-données sur le - composant, notamment le nom de la distribution et la version courante (coté - serveur et interface web) ou encore les sous-cubes utilisés par ce - cube. - -Le strict minimum étant : - -* le fichier ``__pkginfo__.py`` -* la définition du schéma diff -r 972bd504daf6 -r 308037210dab doc/book/fr/03-01-bis-installation.fr.txt --- a/doc/book/fr/03-01-bis-installation.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _installationGAE: - -Installation de `CubicWeb` pour GoogleAppEngine -=============================================== - -Qu'est-ce que `LAX` ? -======================= - -`LAX` (Logilab Appengine eXtension) est un framework d'application -web basé sur `Google AppEngine`. - -`LAX` est un portage de la partie web de la plate-forme applicative -développée par Logilab depuis 2001. Cette plate-forme publie des -données tirées de bases SQL, d'annuaires LDAP et de systèmes de -gestion de version. En avril 2008, elle a été portée pour fonctionner -sur le "datastore" de `Google AppEngine`. - -XXX: faire un parallèle entre Django/GAE et LAX/GAE - - -Téléchargement des sources -========================== - -- Les sources de `Google AppEngine` peuvent être récupérées à l'adresse - suivante : http://code.google.com/appengine/downloads.html - -- Les sources de `LAX` se trouvent à l'adresse suivante : - http://lax.logilab.org/ - - -Installation -============ - -Une fois décompactée, l'archive `lax-0.1.0-alpha.tar.gz`, on obtient -l'arborescence suivante:: - - . - |-- app.yaml - |-- custom.py - |-- data - |-- ginco/ - |-- i18n/ - |-- logilab/ - |-- main.py - |-- mx/ - |-- rql/ - |-- schema.py - |-- simplejson/ - |-- tools/ - | |-- generate_schema_img.py - | `-- i18ncompile.py - |-- views.py - |-- yams/ - `-- yapps/ - - -On retrouve le squelette d'une application web de `Google AppEngine` -(fichiers ``app.yaml``, ``main.py`` en particulier) avec les dépendances -supplémentaires nécessaires à l'utilisation du framework `LAX` - - -Lancement de l'application de base -================================== - -Plusieurs répertoires doivent être accessibles via la variable -d'environnement ``PYTHONPATH`` :: - - $ export PYTHONPATH=/path/to/google_appengine:/path/to/google_appengine/lib/yaml/lib:/path/to/myapp/ - -Le répertoire yaml n'est nécessaire que pour le lancement des scripts -qui se trouvent dans lax/tools et pour l'exécution des tests unitaires. - -Pour démarrer:: - - $ python /path/to/google_appengine/dev_appserver.py /path/to/lax - - diff -r 972bd504daf6 -r 308037210dab doc/book/fr/03-01-installation.fr.txt --- a/doc/book/fr/03-01-installation.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ -.. -*- coding: utf-8 -*- - -Installation -============ - -Installation de Cubicweb et de ses dépendances ----------------------------------------------- - -`CubicWeb` est disponible via un entrepôt Mercurial utilisant l'extension forest. -Vous devez donc dans un premier temps vous assurer que Mercurial est bien installé -et que vous avez l'extension forest. - -Installation de Mercurial -````````````````````````` -Veuillez vous référer a la documentation en ligne du projet Mercurial_. - -.. _Mercurial: http://www.selenic.com/mercurial/wiki/ - -Installation de l'extension forest -`````````````````````````````````` -Dans un premier temps, récupérez une copie des sources via: http://hg.akoha.org/hgforest/. -Ensuite, ajoutez a votre ``~/.hgrc`` les lignes suivantes :: - - [extensions] - hgext.forest= - # or, if forest.py is not in the hgext dir: - # forest=/path/to/forest.py - -Récupération des sources -```````````````````````` -Clonez la foret dans votre répertoire de travail. - -:: - - hg fclone http://www.logilab.org/hg/forests/cubicweb - -.. note:: - Nous vous recommandons de créer un lien symbolique vers l'outil ``cubicweb-ctl`` - que vous allez etre amené a utiliser. - - :: - - $ ln -s /path/to/forest/cubicweb/bin/cubicweb-ctl ~/bin - -Installation de Postgres -```````````````````````` -Veuillez vous référer a la documentation en ligne du projet Postgres_. - -.. _Postgres: http://www.postgresql.org/ - -Vous allez devoir installer les trois paquets suivants: `postgres-8.3`, -`postgres-contrib-8.3` and `postgresql-plpython-8.3`. - - -On pourra ensuite installer les paquets suivants : - -* `pyro` si vous voulez que l'entrepôt soit accessible via Pyro ou si le client - et le serveur ne sont pas sur la même machine (auquel cas il faut installer ce - paquet sur les machines clientes et serveur) - -* `python-ldap` si vous voulez utiliser une source ldap sur le serveur - - -.. _ConfigurationEnv: - -Configuration de l'environnement --------------------------------- - -[FIXME] -Ces variables ne sont plus requises pour le bon fonctionnement de `CubicWeb`, non? -A part rajouter la foret dans le PYTHONPATH, rien de plus ne doit etre fait? - -Mettez à jour votre variable d'environemment PYTHONPATH afin d'y ajouter -le chemin d'acces a votre foret ``cubicweb``. - -Ajouter les lignes suivantes à son `.bashrc` ou `.bash_profile` pour configurer -votre environnement de développement :: - - export PYTHONPATH=/full/path/to/cubicweb-forest - - export ERUDI_REGISTRY=~/etc/erudi.d/ - export ERUDI_TEMPLATES=~/hg/ - export ERUDI_RUNTIME=/tmp/ - -Cela suppose que le composant erudi que vous développez est dans un -sous-répertoire de *~/hg/* et que vous avez créé le répertoire *~/etc/erudi.d/* -pour que `cubicweb-ctl` y place vos instances de test. - -.. _ConfigurationPostgres: - -Configuration Postgres ----------------------- -* Tout d'abord vous devez initialiser votre base de données Postgres via la - commande ``initidb``. - :: - - $ initdb -D /path/to/pgsql - - Une fois ces paquets installés vous pouvez lancer votre server de base de - données Postgres avec la commande suivante: :: - - $ postgres -D /path/to/psql - - Si vous ne pouvez exécuter cette commande pour des raisons de permissions - assurez-vous que votre utilisateur a droit d'écriture sur les la base de données. - - :: - - $ chown username /path/to/pgsql - -* Création d'un super utilisateur pour la création d'instance (**root**) :: - - createuser -s username - - Initialisez le mot de passe de ce superutilisateur ``username`` via - ``su - postgres`` puis ``psql``. - - Un mot de passe de connection pour cet utilisateur vous sera demandé. Il - faudra utiliser ce login / mot de passe à la création d'instance via - `cubicweb-ctl` - -[XXX] -Est-ce que ces etapes sont vraiment necessaires? -sand : lors de l'installation de ma bdd cela n'a pas ete fait -et il semble que tout aille bien. Doit etre verifie avec les experts. - -* installation des extensions pour l'index plein texte :: - - cat /usr/share/postgresql/8.1/contrib/tsearch2.sql | psql -U pgadmin template1 - -* installation du langage plpythonu par défaut :: - - createlang -U pgadmin plpythonu template1 - - -Configuration Pyro ------------------- -Si vous utilisez Pyro, il est nécessaire d'avoir un serveur de noms Pyro -tournant sur votre réseau (par défaut celui-ci est repéré par une requête -broadcast). Pour cela il faut soit : - -* le lancer à la main avant le démarrage de erudi avec la commande `pyro-ns` - -* le lancer à la main avant le démarrage de erudi sous forme d'un serveur avec - la commande `pyro-nsd start` - -* éditer le fichier */etc/default/pyro-nsd* pour que le serveur de nom pyro soit - lancé automatiquement au démarrage de la machine - - diff -r 972bd504daf6 -r 308037210dab doc/book/fr/03-02-create-instance.fr.txt --- a/doc/book/fr/03-02-create-instance.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -.. -*- coding: utf-8 -*- - -=================================== -Creation de votre premiere instance -=================================== - - -Qu'est-ce qu'une instance? -========================== - -Une instance CubicWeb consiste en un dossier situe dans ``~/etc/cubicweb.d`` -qui permettra de lancer une application web. Une instance est cree a partir -d'un ou plusieurs cubes. - -Nous recommandons de ne pas definir de schema, entites ou vues dans l'instance -meme si cela est possible dans un but de re-utilisabilite des entities et de leurs -vues. Nous conseillons plutot de developper des cubes qui pourront par la suite -etre utilises dans d'autres instances (approche modulaire). - -L'instance n'est qu'un conteneur referrant a des cubes et a des parametres -des configuration de l'application web. - -Qu'est-ce qu'un cube? -===================== - -Un cube definit des entities, leur vues, leur schemas et leur workflow -dans un repertoire independant situe dans ``/path/to/forest/cubicweb/cubes/``. - -Lors de la creation d'une instance, vous avez la possibilite de lister -le ou les cubes que votre instance va utiliser. Utiliser un cube signifie -avoir a disposition dans votre instance les entites definies dans le schema -de votre cube ainsi que les vues et les workflows. - - -.. note:: - Les commandes utilisees ci-apres sont detaillees dans la section - dediee a :ref:`cubicweb-ctl`. - - -Création d'un cube -================== - -Commençons par créer un squelette qui nous servira de base au développement de -notre cube ou application :: - - cd ~/hg - - cubicweb-ctl newtemplate moncube - - # répondre aux questions - cd moncube - hg init - hg add . - hg ci - -A partir de là si tout va bien, votre cube devrait être affiché par -`cubicweb-ctl list` dans la section *Available components*, si ce n'est pas le cas -revoir la section :ref:`ConfigurationEnv`. - - -Pour utiliser un cube, il faut le mentionner dans la variable -__use__ du fichier __pkginfo__ de l'application. Cette variable -contrôle à la fois le packaging de l'application (dépendances gérées -par les utilitaires système comme les outils APT) et les composants -effectivement utilisables lors de la création de la base -(import_erschema('Moncomposant') ne fonctionne pas sinon). - -FIXME - need example code :: - - __use__ = ('blog', 'file') - -Création d'une instance de développement -======================================== - -Maintenant que nous avons notre squelette de modèle, on peut en créer une -instance afin de voir ce que tout ça donne dans un simple navigateur web. -Nous allons utiliser une configuration `all-in-one` afin de simplifier les -choses :: - - cubicweb-ctl create -c all-in-one moncube moninstance - -Une série de questions vont être posées, la réponse par défaut est généralement -suffisante. Vous pourrez de toute façon modifier la configuration par la suite -en éditant les fichiers générés. Lorsqu'un login/mot de passe d'accès au sgbd -vous est demandé, il est recommandé d'utiliser l'utilisateur créé lors de la -:ref:`ConfigurationPostgres`. - -Il est important de distinguer ici l'utilisateur utilisé pour accéder au sgbd, -et l'utilisateur utilisé pour s'authentifier dans l'application cubicweb. Lorsque -l'application cubicweb démarre, elle utilise le login/mot de passe sgdb pour -récupérer le schéma et gérer les transactions bas-niveau. En revanche, lorsque -`cubicweb-ctl create` vous demande un login/mot de passe `manager` pour cubicweb, il -s'agit d'un utilisateur qui sera créé dans l'application `cubicweb` pour pouvoir -s'y connecter dans un premier temps et l'administrer. Il sera par la suite possible -de créer des utilisateurs différents pour l'application. - -A l'issue de cette commande, la définition de votre instance se trouve dans -*~/etc/cubicweb.d/moninstance/*. Pour la lancer, il suffit de taper :: - - cubicweb-ctl start -D moninstance - -L'option `-D` indique le *debug mode* : l'instance ne passe pas en mode serveur -et ne se déconnecte pas du terminal, ce qui simplifie le dépannage en cas de non -démarrage de l'instance. Vous pouvez ensuite allez voir ce que ça donne en -pointant votre navigateur sur l'url `http://localhost:8080` (le n° de port -dépend de votre configuration). Pour vous authentifier vous pouvez utiliser le -login/mot de passe administrateur que vous avez spécifié lors de la création de -l'instance. - -Pour arrêter l'instance, un Ctrl-C dans la fenêtre où vous l'avez lancé -suffit. Si l'option `-D` a été omise, il faut taper :: - - cubicweb-ctl stop moninstance - -Voilà, tout est en place pour démarrer le développement du modèle... - - -Utilisation de cubicweb-liveserver ----------------------------------- - -Afin de tester rapidement un nouveau cube, on peut également -utiliser le script `cubicweb-liveserver` qui permet de créer une -application en mémoire (utilisant une base de données SQLite par -défaut) et la rendre accessible via un serveur web:: - - cubicweb-ctl live-server moncomposant - -ou bien, pour utiliser une base de données existante (SQLite ou postgres):: - - cubicweb-ctl live-server -s monfichier_sources moncomposant - diff -r 972bd504daf6 -r 308037210dab doc/book/fr/03-03-cubicweb-ctl.fr.txt --- a/doc/book/fr/03-03-cubicweb-ctl.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _cubicweb-ctl: - -L'outil `cubicweb-ctl` -====================== -`cubicweb-ctl` est le couteau suisse pour la gestion d'instances CubicWeb. -La syntaxe générale est :: - - cubicweb-ctl [options commande] - -Pour voir les commandes disponibles :: - - cubicweb-ctl - cubicweb-ctl --help - -A noter que les commandes disponibles varient en fonction des parties d'CubicWeb -qui sont installées. - -Pour voir l'aide pour une commande spécifiques :: - - cubicweb-ctl --help - -Commandes pour la création d'un cube ------------------------------------- -* ``newcube``, crée un nouveau cube sur le système de fichiers - à partir du nom passé en paramètre. Cette commande crée le cube à partir - d'une squelette d'application, incluant également les fichiers pour le - packaging debian) - -Commandes pour la création d'une instance ------------------------------------------ -* ``create``, crée les fichiers de configuration d'une instance -* ``db-create``, crée la base de données système d'une instance (tables et - extensions uniquement) -* ``db-init``, initialise la base de données système d'une instance (schéma, - groupes, utilisateurs, workflows...) - -Par défaut ces trois commandes sont enchainées. - -Commande pour la création d'une instance pour Google App Engine ---------------------------------------------------------------- -* ``newgapp``, crée les fichiers de configuration d'une instance - -Cette commande doit être suivie de l'exécution de commandes -permettant l'initialisation de la base de données spécifique à -Google App Engine, appellée ``datastore``. - -Pour plus de détails veuillez vous référer à `LAX <>`_ - - -Commandes pour le lancement des instances ------------------------------------------ -* ``start``, démarre une, plusieurs, ou toutes les instances -* ``stop``, arrêt une, plusieurs, ou toutes les instances -* ``restart``, redémarre une, plusieurs, ou toutes les instances -* ``status``, donne l'état des instances - -Commandes pour la maintenance des instances -------------------------------------------- -* ``upgrade``, lance la migration d'instance(s) existante(s) lorsqu'une nouvelle - version d'CubicWeb ou du composant est installée -* ``shell``, ouvre un shell de migration pour la maintenance manuelle d'une instance -* ``db-dump``, crée un dump de la base de données système -* ``db-restore``, restore un dump de la base de données système -* ``db-check``, vérifie l'intégrité des données d'une instance. Si la correction - automatique est activée, il est conseillé de faire un dump avant cette - opération -* ``schema-sync``, , synchronise le schéma persistent d'une instance avec le schéma - de l'application. Il est conseillé de faire un dump avant cette opération - -Commandes pour la maintenance des catalogues i18n -------------------------------------------------- -* ``i18ncubicweb``, regénère les catalogues de messages de la librairie CubicWeb -* ``i18ncube``, regénère les catalogues de messages d'un composant -* ``i18ninstance``, recompile les catalogues de messages d'une instance. Cela est - effectué automatiquement lors d'une upgrade - -Cf :ref:`Internationalisation`. - -Autres commandes ----------------- -* ``list``, donne la liste des configurations, des composants et des instances - disponibles -* ``delete``, supprime une instance (fichiers de configuration et base de données) - - - -Exemples --------- - -Creation d'une instance a partir de cube existant -````````````````````````````````````````````````` - -Afin de creer une instance a partir d'un cube existant, executez la commande -suivant :: - - cubicweb-ctl create - -Cette commande va creer les fichiers de configuration d'une instance dans -``~/etc/cubicweb.d/``. -L'outil ``cubicweb-ctl`` va vous autoriser a executer au sein de ``create`` -les commandes ``db-create`` et ``db-init`` afin de completer la creation de -votre instance en une seule commande. - -Si vous decidez de ne pas le faire lorsque ``cubicweb-ctl create`` vous le -propose, alors n'oubliez pas de lancer ces commandes (``cubicweb-ctl db-create``, -``cubicweb-ctl db-init`` ) par la suite, sinon -votre installation ne sera pas complete. - - -Creation d'une instance a partir d'une nouveau cube -``````````````````````````````````````````````````` - -Creez avant tout votre nouveau cube :: - - cubicweb-ctl newcube - -Cette commande va creer un nouveau cube dans ``/path/to/forest/cubicweb/cubes/``. - - diff -r 972bd504daf6 -r 308037210dab doc/book/fr/03-04-mercurial.fr.txt --- a/doc/book/fr/03-04-mercurial.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ -.. -*- coding: utf-8 -*- - -Présentation de Mercurial -------------------------- - -Introduction -```````````` -Mercurial_ gère un ensemble distribué d'entrepôts contenant des arbres de -révisions (chaque révision indique les changements à effectuer pour obtenir la -version suivante, et ainsi de suite). Localement, on dispose d'un entrepôt -contenant un arbre de révisions, et d'un répertoire de travail. Il est possible -de mettre dans son répertoire de travail, une des versions issue de son entrepôt -local, de la modifier puis de la verser dans son entrepôt. Il est également -possible de récuprer dans son entrepôt local des révisions venant d'un autre -entrepôt, ou d'exporter ses propres révisions depuis son entrepôt local vers un -autre entrepôt. - -A noter que contrairement à CVS/Subversion, on crée généralement un entrepôt par -projet à gérer. - -Lors d'un développement collaboratif, on crée généralement un entrepôt central -accessible à tout les développeurs du projet. Ces entrepôts centraux servent de -référence. Selon ses besoins, chacun peut ensuite disposer d'un entrepôt local, -qu'il faudra penser à synchroniser avec l'entrepôt central de temps à autre. - - -Principales commandes -````````````````````` -* Créer un entrepôt local :: - - hg clone ssh://orion//home/src/prive/rep - -* Voir le contenu de l'entrepôt local (outil graphique en Tk) :: - - hg view - -* Ajouter un sous-répertoire ou un fichier dans le répertoire courant :: - - hg add rep - -* Placer dans son répertoire de travail une révision spécifique (ou la dernière - revision) issue de l'entrepôt local :: - - hg update [identifiant-revision] - hg up [identifiant-revision] - -* Récupérer dans son entrepôt local, l'arbre de révisions contenu dans un - entrepôt distant (cette opération ne modifie pas le répertoire local) :: - - hg pull ssh://orion//home/src/prive/rep - hg pull -u ssh://orion//home/src/prive/rep # équivalent à pull + update - -* Voir quelles sont les têtes de branches de l'entrepôt local si un `pull` a - tiré une nouvelle branche :: - - hg heads - -* Verser le répertoire de travail dans l'entrepôt local (et créer une nouvelle - révision) :: - - hg commit - hg ci - -* Fusionner, avec la révision mère du répertoire local, une autre révision issue - de l'entrepôt local (la nouvelle révision qui en résultera aura alors deux - révisions mères) :: - - hg merge identifiant-revision - -* Exporter dans un entrepôt distant, l'arbre de révisions contenu dans son - entrepôt local (cette opération ne modifie pas le répertoire local) :: - - hg push ssh://orion//home/src/prive/rep - -* Voir quelle sont les révisions locales non présentes dans un autre entrepôt :: - - hg outgoing ssh://orion//home/src/prive/rep - -* Voir quelle sont les révisions d'un autre entrepôt non présentes localement :: - - hg incoming ssh://orion//home/src/prive/rep - -* Voir quelle est la révision issue de l'entrepôt local qui a été sortie dans le - répertoire de travail et modifiée :: - - hg parent - -* Voir les différences entre le répertoire de travail et la révision mère de - l'entrepôt local, éventuellement permettant de les verser dans l'entrepôt - local :: - - hg diff - hg commit-tool - hg ct - - -Bonnes pratiques -```````````````` -* penser à faire un `hg pull -u` régulièrement et particulièrement avant de - faire un `hg commit` - -* penser à faire un `hg push` lorsque votre entrepôt contient une version - relativement stable de vos modifications - -* si un `hg pull -u` a créé une nouvelle tête de branche : - - 1. identifier l'identifiant de celle-ci avec `hg head` - 2. fusionner avec `hg merge` - 3. `hg ci` - 4. `hg push` - -.. _Mercurial: http://www.selenic.com/mercurial/ diff -r 972bd504daf6 -r 308037210dab doc/book/fr/03-XX-external_resources.fr.txt --- a/doc/book/fr/03-XX-external_resources.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -.. -*- coding: utf-8 -*- - - -Les ressources externes -======================= - -Les ressources externes à une application regroupent l'ensemble des fichiers qui seront chargés dans l'entête des pages XHTML générées. -Elles sont donc constituées principalement des feuilles de styles, des scripts javascripts et de certaines ressources graphiques comme l'icône favicon par exemple. - - -Liste des feuilles de styles utilisées par défaut -------------------------------------------------- - -Les fichiers par défaut se trouve dans les sources du framework. En voici le tableau récapitulatif: - -+--------------------------------------+----------------------------------------------------+-----------------------------------+ -| Fichiers | Utilisation | Vues ou widget concernés | -+======================================+====================================================+===================================+ -| web/data/cubicweb.acl.css | formulaires pour le contrôle aux accès | editgroups, security | -| web/data/cubicweb.calendar.css | calendriers | onemonthcal, oneweekcal | -| web/data/cubicweb.calendar_popup.css | popup calendriers | DateWidget | -| web/data/cubicweb.css | gabarit principal de l'application | | -| web/data/cubicweb.facets.css | surcharge du `MIT Simile Exhibit Web Widgets`_ | filter_box | -| web/data/cubicweb.form.css | formulaires | creation, inline-creation, copya, | -| | | inline-edition, edition, muledit | -| web/data/cubicweb.html_tree.css | style pour les widgets d'arborescence | | -| web/data/cubicweb.ie.css | dédié aux comportements de Internet Explorer | | -| web/data/cubicweb.iprogress.css | style pour les widgets d'avancement | | -| web/data/cubicweb.login.css | page et popup d'authentification | logform | -| web/data/cubicweb.mailform.css | style utilisé dans les formulaires d'envoi de mail | | -| web/data/cubicweb.preferences.css | style pour la page des préférences utilisateurs | systemepropertiesform | -| web/data/cubicweb.print.css | style dédié à l'impression | | -| web/data/cubicweb.schema.css | style dédié au schéma de l'application | | -| web/data/cubicweb.suggest.css | surcharge utilisée pour les suggestions | | -| web/data/cubicweb.tablesorter.css | surcharge pour le tri dans les tableau | | -| web/data/cubicweb.tableview.css | surcharge pour le tri sélectif | | -| web/data/cubicweb.timetable.css | style pour le widget Timetable | timetable | -| web/data/jquery.autocomplete.css | surcharge pour le widget `jQuery autocompleter`_ | | -| web/data/jquery.treeview.css | surcharge pour le widget `jQuery treeview`_ | | -| web/data/pygments.css | style pour la coloration des blocs de code | | -| web/data/timeline-bundle.css | surcharge du `MIT Simile Timeline Web Widgets`_ | TimelineWidget | -| web/data/ui.tabs.css | surcharge pour le widget Tabs de `jQuery UI`_ | | -+--------------------------------------+----------------------------------------------------+-----------------------------------+ - -.. _MIT Simile Exhibit Web Widgets: http://code.google.com/p/simile-widgets/wiki/Exhibit -.. _MIT Simile Timeline Web Widgets: http://code.google.com/p/simile-widgets/wiki/Timeline -.. _jQuery autocompleter: http://www.dyve.net/jquery/?autocomplete -.. _jQuery treeview: http://plugins.jquery.com/project/treeview -.. _jQuery UI: http://docs.jquery.com/UI - -D'une manière générale, si vous réutiliser un nom de fichier existant, vous écrasez le contenu du fichier d'origine. - - -Changer les feuilles de styles ------------------------------- - -Configuration statique -~~~~~~~~~~~~~~~~~~~~~~ -Dans les sources de votre nouveau cube, vous devez éditer le fichier *data/external_resources* et définir la variable de configuration: - - # CSS stylesheets to include in HTML headers - # uncomment the line below to use template specific stylesheet - STYLESHEETS = DATADIR/cubicweb.css - -Les styles sont définis dans le fichier external_resources par 3 variables: - -- la variable STYLESHEETS est défine pour tous les types de médias -- la variable STYLESHEETS_PRINT sont les styles applicables pour l'impression -- la variable IE_STYLESHEETS s'appliquent uniquement aux versions d'Internet Explorer - -En copiant le fichier d'origine **cubicweb.css**, il est alors possible de modifier le gabarit de base du framework CubicWeb. -Il est également possible de réutiliser le fichier d'origine. - -En créant un nouveau fichier **cubes.(le_nom_du_cube).css** dans le répertoire **data/** et en ajoutant une directive css @import, il est possible de réutiliser les styles définis par défaut: - - @import url("cubicweb.css"); - - -Chargement dynamique de feuilles de style dans vos vues -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Il est possible de charger des css spécifiques pour une vue par l'utilisation de la méthode add_css(): - - self.req.add_css('mon_cube.css') - - -Les ressources graphiques de base ---------------------------------- - -Vous pouvez changer certaines ressources graphiques comme: - -- le logo du site: - - # path to the logo (relative to the application main script, seen as a - # directory, hence .. when you are not using an absolute path) - LOGO = DATADIR/logo.png - -- la 'favicon' du site: - - FAVICON = DATADIR/favicon.ico - -- le logo des flux RSS: - - RSS_LOGO = DATADIR/rss.png - -- l'icône permettant l'appel au widget 'calendrier': - - CALENDAR_ICON = DATADIR/calendar.png - -- l'icône utilisée pour la validation d'une recherche: - - SEARCH_GO = DATADIR/go.png - -- l'icône d'aide en ligne: - - HELP = DATADIR/help.png - - -Ajouter vos scripts javascripts -------------------------------- - -Configuration statique -~~~~~~~~~~~~~~~~~~~~~~ -Vous devez surcharger la variable JAVASCRIPTS dans le fichier *data/external_resources* de votre cube. - -Chargement dynamique de script javascript dans vos vues -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Il est possible de charger vos scripts par la méthode add_js(): - - self.req.add_js('mon_script.js') - - -Problèmes connus ----------------- - -Il est important de noter que la méthode de chargement dynamique ne marche pas avec les widgets Ajax. Vos fichiers devront déjà être au préalable avoir été chargés. diff -r 972bd504daf6 -r 308037210dab doc/book/fr/03-setup.fr.txt --- a/doc/book/fr/03-setup.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _MiseEnPlaceEnv: - -Installation et mise en place d'un environnement `CubicWeb` -=========================================================== - -.. toctree:: - :maxdepth: 1 - - 03-01-installation.fr.txt - 03-02-create-instance.fr.txt - 03-03-cubicweb-ctl.fr.txt - 03-04-mercurial.fr.txt - diff -r 972bd504daf6 -r 308037210dab doc/book/fr/04-01-schema-stdlib.fr.txt --- a/doc/book/fr/04-01-schema-stdlib.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -.. -*- coding: utf-8 -*- - -Schémas prédéfinies dans la librairie -------------------------------------- - -La librairie définit un certain nombre de schémas d'entités nécessaires -au système ou bien couramment utilisées dans les application `cubicweb`. -Vous pouvez bien entendu étendre ces schémas au besoin. - - -Schémas "systèmes" -`````````````````` - -* `CWUser`, utilisateurs du système -* `CWGroup`, groupes d'utilisateurs -* `CWEType`, types d'entité -* `CWRType`, types de relation - -* `State`, état d'un workflow -* `Transition`, transition d'un workflow -* `TrInfo`, enregistrement d'un passage de transition pour une entité - -* `EmailAddress`, adresse électronique, utilisé par le système de notification - pour les utilisateurs et par d'autres schéma optionnels - -* `CWProperty`, utilisé pour configurer l'application -* `CWPermission`, utilisé pour configurer la sécurité de l'application - -* `Card`, fiche documentaire générique -* `Bookmark`, un type d'entité utilisé pour permetter à un utilisateur de - personnaliser ses liens de navigation dans l'application. - - -Composants de la librairie -`````````````````````````` -Une application est construite sur la base de plusieurs composants de base. -Parmi les composants de base disponible, on trouve par exemple : - -* `ecomment`, fournit le type d'entité `Comment` permettant de commenter les - entités du site - -* `emailinglist`, fournit le type d'entité `Mailinglist` regroupant des - informations sur une liste de discussion - -* `efile`, fournit les types d'entités `File` et `Image` utilisés pour - représenter des fichiers (texte ou binaire) avec quelques données - supplémentaires comme le type MIME ou l'encodage le cas échéant (). - -* `elink`, fournit le type d'entité lien internet (`Link`) - -* `eblog`, fournit le type d'entité weblog (`Blog`) - -* `eperson`, fournit le type d'entité personne physique (`Person`) - -* `eaddressbook`, fournit les types d'entités utilisés pour représenter des n° - de téléphone (`PhoneNumber`) et des adresses postales (`PostalAddress`) - -* `eclasstags`, système de classfication à base d'étiquettes (`Tag`) - -* `eclassfolders`, système de classification à base de dossiers hiérarchiques - destinés à créer des rubriques de navigation (`Folder`) - -* `eemail`, gestion d'archives de courriers électroniques (`Email`, `Emailpart`, - `Emailthread`) - -* `ebasket`, gestion de paniers (`Basket`) permettant de regrouper des entités - -Pour déclarer l'utilisation d'un composant, une fois celui-ci installé, ajoutez -le nom du composant à la variable `__use__` du fichier `__pkginfo__.py` de -votre propre composant. diff -r 972bd504daf6 -r 308037210dab doc/book/fr/04-02-schema-definition.fr.txt --- a/doc/book/fr/04-02-schema-definition.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,361 +0,0 @@ -.. -*- coding: utf-8 -*- - -Définition d'un type d'entité ------------------------------ - -Un type d'entité est définit par une classe python héritant de `EntityType`. Le -nom de la classe correspond au nom du type. Ensuite le corps de la classe -contient la description des attributs et des relations pour ce type d'entité, -par exemple :: - - class Personne(EntityType): - """une personne avec les propriétés et relations nécessaires à mon - application""" - - nom = String(required=True, fulltextindexed=True) - prenom = String(required=True, fulltextindexed=True) - civilite = String(vocabulary=('M', 'Mme', 'Mlle')) - date_naiss = Date() - travaille_pour = SubjectRelation('Company', cardinality='?*') - -* le nom de l'attribut python correspond au nom de l'attribut ou de la relation - dans cubicweb. - -* tout les types de bases sont disponibles nativement : `String`, `Int`, `Float`, - `Boolean`, `Date`, `Datetime`, `Time`, `Byte`. - -* Chaque type d'entité a au moins les méta-relations suivantes : - - - `eid` (`Int`) - - - `creation_date` (`Datetime`) - - - `modification_date` (`Datetime`) - - - `created_by` (`CWUser`) (quel utilisateur a créé l'entité) - - - `owned_by` (`CWUser`) (à qui appartient l'entité, par défaut le - créateur mais pas forcément et il peut exister plusieurs propriétaires) - - - `is` (`CWEType`) - - -* il est également possible de définir des relations dont le type d'entité est - l'objet en utilisant `ObjectRelation` plutôt que `SubjectRelation` - -* le premier argument de `SubjectRelation` et `ObjectRelation` donne - respectivement le type d'entité objet /sujet de la relation. Cela - peut être : - - * une chaine de caractères correspondant à un type d'entité - - * un tuple de chaines de caractères correspondant à plusieurs types d'entité - - * les chaînes de caractères spéciales suivantes : - - - "**" : tout les types d'entité - - "*" : tout les types d'entité non méta - - "@" : tout les types d'entité méta mais non "système" (i.e. servant à la - description du schema en base) - -* il est possible d'utiliser l'attribut possible `meta` pour marquer un type - d'entité comme étant "méta" (i.e. servant à décrire / classifier d'autre - entités) - -* propriétés optionnelles des attributs et relations : - - - `description` : chaine de caractères décrivant un attribut ou une - relation. Par défaut cette chaine sera utilisée dans le formulaire de saisie - de l'entité, elle est donc destinée à aider l'utilisateur final et doit être - marquée par la fonction `_` pour être correctement internationalisée. - - - `constraints` : liste de contraintes devant être respecté par la relation - (c.f. `Contraintes`_) - - - `cardinality` : chaine de 2 caractères spécifiant la cardinalité de la - relation. Le premier caractère donne la cardinalité de la relation sur le - sujet, le 2eme sur l'objet. Quand une relation possède plusieurs sujets ou - objets possibles, la cardinalité s'applique sur l'ensemble et non un à un (et - doit donc à priori être cohérente...). Les valeurs possibles sont inspirées - des expressions régulières : - - * `1`: 1..1 - * `?`: 0..1 - * `+`: 1..n - * `*`: 0..n - - - `meta` : booléen indiquant que la relation est une méta relation (faux par - défaut) - -* propriétés optionnelles des attributs : - - - `required` : booléen indiquant si l'attribut est obligatoire (faux par - défaut) - - - `unique` : booléen indiquant si la valeur de l'attribut doit être unique - parmi toutes les entités de ce type (faux par défaut) - - - `indexed` : booléen indiquant si un index doit être créé dans la base de - données sur cette attribut (faux par défaut). C'est utile uniquement si vous - savez que vous allez faire de nombreuses recherche sur la valeur de cet - attribut. - - - `default` : valeur par défaut de l'attribut. A noter que dans le cas des - types date, les chaines de caractères correspondant aux mots-clés RQL - `TODAY` et `NOW` sont utilisables. - - - `vocabulary` : spécifie statiquement les valeurs possibles d'un attribut - -* propriétés optionnelles des attributs de type `String` : - - - `fulltextindexed` : booléen indiquant si l'attribut participe à l'index plein - texte (faux par défaut) (*valable également sur le type `Byte`*) - - - `internationalizable` : booléen indiquant si la valeur de cet attribut est - internationalisable (faux par défaut) - - - `maxsize` : entier donnant la taille maximum de la chaine (pas de limite par - défaut) - -* propriétés optionnelles des relations : - - - `composite` : chaîne indiquant que le sujet (composite == 'subject') est - composé de ou des objets de la relation. Pour le cas opposé (l'objet est - composé de ou des sujets de la relation, il suffit de mettre 'object' comme - valeur. La composition implique que quand la relation est supprimé (et donc - aussi quand le composite est supprimé), le ou les composés le sont - également. - -Contraintes -``````````` -Par défaut les types de contraintes suivant sont disponibles : - -* `SizeConstraint` : permet de spécifier une taille minimale et/ou maximale sur - les chaines de caractères (cas générique de `maxsize`) - -* `BoundConstraint` : permet de spécifier une valeur minimale et/ou maximale sur - les types numériques - -* `UniqueConstraint` : identique à "unique=True" - -* `StaticVocabularyConstraint` : identique à "vocabulary=(...)" - -* `RQLConstraint` : permet de spécifier une requête RQL devant être satisfaite - par le sujet et/ou l'objet de la relation. Dans cette requête les variables `S` - et `O` sont préféfinies respectivement comme l'entité sujet et objet de la - relation - -* `RQLVocabularyConstraint` : similaire à la précédente, mais exprimant une - contrainte "faible", i.e. servant uniquement à limiter les valeurs apparaissant - dans la liste déroulantes du formulaire d'édition, mais n'empêchant pas une - autre entité d'être séléctionnée - - -Définition d'un type de relation --------------------------------- - -Un type de relation est définit par une classe python héritant de `RelationType`. Le -nom de la classe correspond au nom du type. Ensuite le corps de la classe -contient la description des propriétés de ce type de relation, ainsi -qu'éventuellement une chaine pour le sujet et une autre pour l'objet permettant -de créer des définitions de relations associées (auquel cas il est possibles de -donner sur la classe les propriétés de définition de relation explicitées -ci-dessus), par exemple :: - - class verrouille_par(RelationType): - """relation sur toutes les entités applicatives indiquant que celles-ci sont vérouillées - inlined = True - cardinality = '?*' - subject = '*' - object = 'CWUser' - -En plus des permissions, les propriétés propres aux types de relation (et donc -partagés par toutes les définitions de relation de ce type) sont : - -* `inlined` : booléen contrôlant l'optimisation physique consistant à stocker la - relation dans la table de l'entité sujet au lieu de créer une table spécifique - à la relation. Cela se limite donc aux relations dont la cardinalité - sujet->relation->objet vaut 0..1 ('?') ou 1..1 ('1') - -* `symmetric` : booléen indiquant que la relation est symétrique. i.e. - `X relation Y` implique `Y relation X` - -Dans le cas de définitions de relations simultanée, `sujet` et `object` peuvent -tout deux valoir la même chose que décrite pour le 1er argument de -`SubjectRelation` et `ObjectRelation`. - -A partir du moment où une relation n'est ni mise en ligne, ni symétrique, et -ne nécessite pas de permissions particulières, sa définition (en utilisant -`SubjectRelation` ou `ObjectRelation`) est suffisante. - - -Définition des permissions --------------------------- - -La définition des permissions se fait à l'aide de l'attribut `permissions` des -types d'entité ou de relation. Celui-ci est un dictionnaire dont les clés sont -les types d'accès (action), et les valeurs les groupes ou expressions autorisées. - -Pour un type d'entité, les actions possibles sont `read`, `add`, `update` et -`delete`. - -Pour un type de relation, les actions possibles sont `read`, `add`, et `delete`. - -Pour chaque type d'accès, un tuple indique le nom des groupes autorisés et/ou -une ou plusieurs expressions RQL devant être vérifiées pour obtenir -l'accès. L'accès est donné à partir du moment où l'utilisateur fait parti d'un -des groupes requis ou dès qu'une expression RQL est vérifiée. - -Les groupes standards sont : - -* `guests` - -* `users` - -* `managers` - -* `owners` : groupe virtuel correspondant au propriétaire d'une entité. Celui-ci - ne peut être utilisé que pour les actions `update` et `delete` d'un type - d'entité. - -Il est également possible d'utiliser des groupes spécifiques devant être pour -cela créés dans le precreate de l'application (`migration/precreate.py`). - -Utilisation d'expression RQL sur les droits en écriture -``````````````````````````````````````````````````````` -Il est possible de définir des expressions RQL donnant des droits de -modification (`add`, `delete`, `update`) sur les types d'entité et de relation. - -Expression RQL pour les permissions sur un type d'entité : - -* il faut utiliser la classe `ERQLExpression` - -* l'expression utilisée correspond à la clause WHERE d'une requête RQL - -* dans cette expression, les variables X et U sont des références prédéfinies - respectivement sur l'entité courante (sur laquelle l'action est vérifiée) et - sur l'utilisateur ayant effectué la requête - -* il est possible d'utiliser dans cette expression les relations spéciales - "has__permission" dont le sujet est l'utilisateur et l'objet une - variable quelquonque, signifiant ainsi que l'utilisateur doit avoir la - permission d'effectuer l'action sur la ou les entités liées cette - variable - -Pour les expressions RQL sur un type de relation, les principes sont les mêmes -avec les différences suivantes : - -* il faut utiliser la classe `RRQLExpression` dans le cas d'une relation non - finale - -* dans cette expression, les variables S, O et U sont des références - prédéfinies respectivement sur le sujet et l'objet de la relation - courante (sur laquelle l'action est vérifiée) et sur l'utilisateur - ayant effectué la requête - -* On peut aussi définir des droits sur les attributs d'une entité (relation non - finale), sachant les points suivants : - - - pour définir des expressions rql, il faut utiliser la classe `ERQLExpression` - dans laquelle X représentera l'entité auquel appartient l'attribut - - - les permissions 'add' et 'delete' sont équivalentes. En pratique seul - 'add'/'read' son pris en considération - - -En plus de cela, le type d'entité `CWPermission` de la librairie standard permet -de construire des modèles de sécurités très complexes et dynamiques. Le schéma -de ce type d'entité est le suivant : :: - - - class CWPermission(MetaEntityType): - """entity type that may be used to construct some advanced security configuration - """ - name = String(required=True, indexed=True, internationalizable=True, maxsize=100) - require_group = SubjectRelation('CWGroup', cardinality='+*', - description=_('groups to which the permission is granted')) - require_state = SubjectRelation('State', - description=_("entity'state in which the permission is applyable")) - # can be used on any entity - require_permission = ObjectRelation('**', cardinality='*1', composite='subject', - description=_("link a permission to the entity. This " - "permission should be used in the security " - "definition of the entity's type to be useful.")) - - -Exemple de configuration extrait de *jpl* :: - - ... - - class Version(EntityType): - """a version is defining the content of a particular project's release""" - - permissions = {'read': ('managers', 'users', 'guests',), - 'update': ('managers', 'logilab', 'owners',), - 'delete': ('managers', ), - 'add': ('managers', 'logilab', - ERQLExpression('X version_of PROJ, U in_group G,' - 'PROJ require_permission P, P name "add_version",' - 'P require_group G'),)} - - ... - - class version_of(RelationType): - """link a version to its project. A version is necessarily linked to one and only one project. - """ - permissions = {'read': ('managers', 'users', 'guests',), - 'delete': ('managers', ), - 'add': ('managers', 'logilab', - RRQLExpression('O require_permission P, P name "add_version",' - 'U in_group G, P require_group G'),) - } - inlined = True - -Cette configuration suppose indique qu'une entité `CWPermission` de nom -"add_version" peut-être associée à un projet et donner le droit de créer des -versions sur ce projet à des groupes spécifiques. Il est important de noter les -points suivants : - -* dans ce cas il faut protéger à la fois le type d'entité "Version" et la - relation liant une version à un projet ("version_of") - -* du fait de la généricité du type d'entité `CWPermission`, il faut effectuer - l'unification avec les groupes et / ou les états le cas échéant dans - l'expression ("U in_group G, P require_group G" dans l'exemple ci-dessus) - - -Utilisation d'expression RQL sur les droits en lecture -`````````````````````````````````````````````````````` -Les principes sont les mêmes mais avec les restrictions suivantes : - -* on ne peut de `RRQLExpression` sur les types de relation en lecture - -* les relations spéciales "has__permission" ne sont pas utilisables - - -Note sur l'utilisation d'expression RQL sur la permission 'add' -``````````````````````````````````````````````````````````````` -L'utilisation d'expression RQL sur l'ajout d'entité ou de relation pose -potentiellement un problème pour l'interface utilisateur car si l'expression -utilise l'entité ou la relation à créer, on est pas capable de vérifier les -droits avant d'avoir effectué l'ajout (noter que cela n'est pas un problème coté -serveur rql car la vérification des droits est effectuée après l'ajout -effectif). Dans ce cas les méthodes de vérification des droits (check_perm, -has_perm) peuvent inidquer qu'un utilisateur n'a pas le droit d'ajout alors -qu'il pourrait effectivement l'obtenir. Pour palier à ce soucis il est en général -nécessaire dans tel cas d'utiliser une action reflétant les droits du schéma -mais permettant de faire la vérification correctement afin qu'elle apparaisse -bien le cas échéant. - -Mise à jour du schema -````````````````````` - -Il faut ensuite lancer son cubicweb en mode shell :: - - cubicweb-ctl shell moninstance - -Et taper :: - - add_entity_type('Personne') - -Et on relance l'application! diff -r 972bd504daf6 -r 308037210dab doc/book/fr/04-define-schema.fr.txt --- a/doc/book/fr/04-define-schema.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -.. -*- coding: utf-8 -*- - -Définition du modèle de données (*schéma*) -========================================== - -Le schéma est l'élément central d'une application d'CubicWeb, définissant le modèle -de données manipulé. Il est généralement défini à partir de type d'entités -existants dans la librairie et d'autres spécifiques, généralement décrites dans -un ou plusieurs fichiers python dans le sous-répertoire `schema` du modèle. - -A ce niveau il est important de noter la différence entre type de relation et -définition de relation : un type de relation est uniquement un nom de relation -avec éventuellement quelques propriétés supplémentaires (voir plus bas), alors -qu'une définition de relation est un triplet complet " - ". Eventuellement un type de relation -sera créé implicitement si aucun n'est associé à une définition de relation du -schema. - -.. include:: 04-01-schema-stdlib.fr.txt -.. include:: 04-02-schema-definition.fr.txt - diff -r 972bd504daf6 -r 308037210dab doc/book/fr/05-01-views-stdlib.fr.txt --- a/doc/book/fr/05-01-views-stdlib.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -.. -*- coding: utf-8 -*- - -Vues prédéfinies dans la librairie ----------------------------------- -Un certain nombre de vues sont utilisées pour construire l'interface web, qui -s'appliquent à une ou plusieurs entités. On les distingue par leur identifiant, -et les principales sont : - -:primary: - vue principale pour une entité, elle est appelée par défaut lorsqu'il n'y a - qu'un seul élément correspondant à la recherche. Cette vue est censée - afficher le maximum d'informations à propos de l'objet. -:secondary: - vue secondaire d'une entité. Par défaut, Elle affiche les deux premiers - attributs de l'entité sous la forme d'un lien cliquable amenant sur la vue - primaire. -:oneline: - similaire à la vue `secondary`, mais appelée dans des cas où l'on désire que - la vue tient sur une ligne, ou de manière générale juste avoir une vue plus - abbrégée. Par défaut, cette vue utilise le paramètre de configuration - `MAX_LINE_CHAR` pour contrôler la taille du résultat. -:text: - similaire à la vue `oneline`, mais ne devant pas contenir de html. -:incontext, outofcontext: - similaire à la vue `secondary`, mais appelé si l'entité est considérée comme - en dehors ou dans son contexte. Par défault renvoie respectivement le - résultat de `textincontext` et `textoutofcontext` entouré par un lien - permettant d'accéder à la vue primaire de l'entité -:textincontext, textoutofcontext: - similaire à la vue `text`, mais appelé si l'entité est considérée comme - en dehors ou dans son contexte. Par défault renvoie respectivement le - résultat des méthodes `.dc_title` et `.dc_long_title` de l'entité -:list: - crée une liste html (
    ) et appelle la vue `listitem` pour chaque entité -:listitem: - redirige par défaut vers la vue `outofcontext` -:rss: - crée unvue RSS/XML et appelle la vue `rssitem` pour chaque entité -:rssitem: - crée unvue RSS/XML pour une entité à partir des résultats renvoyés par les - méthodes dublin core de l'objet (`dc_*`) - -Vues de départ : - -:index: - page d'acceuil -:schema: - affiche le schéma de l'application - -Vues particulières : - -:noresult: - appelé si le result set est vide -:finall: - affiche la valeur de la cellule sans transformation (dans le cas d'une - entité non finale, on voit son eid). Appelable sur n'importe quel result - set. -:table: - crée une table html () et appelle la vue `cell` pour chaque cellule - du résultat. Appelable sur n'importe quel result set. -:cell: - par défaut redirige sur la vue `final` si c'est une entité finale - ou sur la vue `outofcontext` sinon -:null: - vue toujours appelable et ne retournant rien diff -r 972bd504daf6 -r 308037210dab doc/book/fr/05-define-views.fr.txt --- a/doc/book/fr/05-define-views.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,247 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _DefinitionVues: - -Définition de vues -================== - -Les classes de base des vues ----------------------------- - -La class `View` (`cubicweb.common.view`) -```````````````````````````````````````` -Un vue écrit dans son flux de sortie via son attribut `w` (`UStreamIO`). - -L'interface de base des vues est la suivante : - -* `dispatch(**context)`, appelle ("rend") la vue en appellent `call` ou - `cell_call` en fonction des arguments passé -* `call(**kwargs)`, appelle la vue pour un result set complet ou nul -* `cell_call(row, col, **kwargs)`, appelle la vue pour une cellule donnée d'un - result set -* `url()`, retourne l'url permettant d'obtenir cette vue avec le result set en - cours -* `view(__vid, rset, __fallback_vid=None, **kwargs)`, appelle la vue - d'identificant `__vid` sur le result set donné. Il est possible de données un - identificant de vue de "fallback" qui sera utilisé si la vue demandée n'est - pas applicable au result set - -* `wview(__vid, rset, __fallback_vid=None, **kwargs)`, pareil que `view` mais - passe automatiquement le flux en argument - -* `html_headers()`, retourne une liste d'en-tête HTML à placer par le template - principal - -* `page_title()`, retourne le titre à utiliser dans l'en tête HTML `title` - -* `creator(eid)`, retourne l'eid et le login du créateur de l'entité ayant - l'eid passé en argument - -Autres classes de base : - -* `EntityView`, vue s'appliquant à aux lignes ou cellule contenant une entité - (eg un eid) -* `StartupView`, vue de départ n'ayant pas besoin de result set -* `AnyRsetView`, vue s'appliquant à n'importe quelle result set - -Le mecanisme de selection de vues ---------------------------------- - -Pour un identifiant de vue donne, plusieurs vues peuvent etre definies. -`CubicWeb` utilise un selecteur qui permet de calculer un score et d'identifier -la vue la plus appropriee a appliquer dans le contexte. La librairie du selecteur -se trouve dans ``cubicweb.common.selector`` et une librairie des methodes utilisees -pour calculer les scores est dans ``cubicweb.vregistry.vreq``. - -[FROM-LAX-BOOK] - -Tip: when modifying views, you do not need to restart the local -server. Just save the file in your editor and reload the page in your -browser to see the changes. - -With `LAX`, views are defined by Python classes. A view includes : - -- an identifier (all objects in `LAX` are entered in a registry - and this identifier will be used as a key) - -- a filter to select the resulsets it can be applied to - -`LAX` provides a lot of standard views, for a complete list, you -will have to read the code in directory ``ginco/web/views/`` (XXX -improve doc). - -For example, the view named ``primary`` is the one used to display -a single entity. - -If you want to change the way a ``BlogEntry`` is displayed, just -override the view ``primary`` in ``BlogDemo/views.py`` :: - - 01. from ginco.web.views import baseviews - 02. - 03. class BlogEntryPrimaryView(baseviews.PrimaryView): - 04. - 05. accepts = ('BlogEntry',) - 06. - 07. def cell_call(self, row, col): - 08. entity = self.rset.get_entity(row, col) - 09. self.w(u'

    %s

    ' % entity.title) - 10. self.w(u'

    published on %s in category %s

    ' % \ - 11. (entity.publish_date.strftime('%Y-%m-%d'), entity.category)) - 12. self.w(u'

    %s

    ' % entity.text) - -The above source code defines a new primary view (`line 03`) for -``BlogEntry`` (`line 05`). - -Since views are applied to resultsets and resulsets can be tables of -data, it is needed to recover the entity from its (row,col) -coordinates (`line 08`). We will get to this in more detail later. - -The view has a ``self.w()`` method that is used to output data. Here `lines -09-12` output HTML tags and values of the entity's attributes. - -When displaying same blog entry as before, you will notice that the -page is now looking much nicer. - -.. image:: images/lax-book.09-new-view-blogentry.fr.png - :alt: blog entries now look much nicer - -Let us now improve the primary view of a blog :: - - 01. class BlogPrimaryView(baseviews.PrimaryView): - 02. - 03. accepts = ('Blog',) - 04. - 05. def cell_call(self, row, col): - 06. entity = self.rset.get_entity(row, col) - 07. self.w(u'

    %s

    ' % entity.title) - 08. self.w(u'

    %s

    ' % entity.description) - 09. rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid) - 10. self.wview('primary', rset) - -In the above source code, `lines 01-08` are similar to the previous -view we defined. - -At `line 09`, a simple request in made to build a resultset with all -the entities linked to the current ``Blog`` entity by the relationship -``entry_of``. The part of the framework handling the request knows -about the schema and infer that such entities have to be of the -``BlogEntry`` kind and retrieves them. - -The request returns a selection of data called a resultset. At -`line 10` the view 'primary' is applied to this resultset to output -HTML. - -**This is to be compared to interfaces and protocols in object-oriented -languages. Applying a given view to all the entities of a resultset only -requires the availability, for each entity of this resultset, of a -view with that name that can accepts the entity.** - -Assuming we added entries to the blog titled `MyLife`, displaying it -now allows to read its description and all its entries. - -.. image:: images/lax-book.10-blog-with-two-entries.fr.png - :alt: a blog and all its entries - -**Before we move forward, remember that the selection/view principle is -at the core of `LAX`. Everywhere in the engine, data is requested -using the RQL language, then HTML/XML/text/PNG is output by applying a -view to the resultset returned by the query. That is where most of the -flexibility comes from.** - -[WRITE ME] - -* implementing interfaces, calendar for blog entries -* show that a calendar view can export data to ical - -We will implement the ginco.interfaces.ICalendarable interfaces on -entities.BloEntry and apply the OneMonthCalendar and iCalendar views -to resultsets like "Any E WHERE E is BlogEntry" - -* create view "blogentry table" with title, publish_date, category - -We will show that by default the view that displays -"Any E,D,C WHERE E publish_date D, E category C" is the table view. -Of course, the same can be obtained by calling -self.wview('table',rset) - -* in view blog, select blogentries and apply view "blogentry table" -* demo ajax by filtering blogentry table on category - -we did the same with 'primary', but with tables we can turn on filters -and show that ajax comes for free. -[FILLME] - -Les templates ou patron ------------------------ - -Les patrons (ou *template*) sont des cas particulier de vue ne dépendant a -priori pas d'un result set. La classe de base `Template` (`cubicweb.common.view`) -est une classe dérivée de la classe `View`. - -Pour construire une page HTML, un *template principal* est utilisé. Généralement -celui possédant l'identifiant 'main' est utilisé (ce n'est pas le cas lors -d'erreur dans celui-ci ou pour le formulaire de login par exemple). Ce patron -utilise d'autres patrons en plus des vues dépendants du contenu pour générer la -page à renvoyer. - -C'est ce template qui est chargé : - -1. d'éxécuter la requête RQL des données à afficher le cas échéant -2. éventuellement de déterminer la vue à utiliser pour l'afficher si non - spécifiée -3. de composer la page à retourner - - -Le patron principal par défaut (`cubicweb.web.views.basetemplates.TheMainTemplate`) ------------------------------------------------------------------------------------ - -Le template principal par défaut construit la page selon la décomposition -suivante : - -.. image:: images/main_template_layout.png - -Le rectancle contenant le `view.dispatch()` représente l'emplacement où est -inséré la vue de contenu à afficher. Les autres représentent des sous-templates -appelé pour construire la page. Les implémentations par défaut de tout ces -templates sont dans le module `cubicweb.web.views.basetemplates`. Vous pouvez -évidemment surcharger l'un des sous-templates pour modifier l'aspect visuel -d'une partie désirée de la page. - -On peut également contrôler certains comportements du template principal à -l'aide des paramètres de formulaire suivante : - -* `__notemplate`, si présente (quelque soit la valeur associée), seule la vue de - contenu est renvoyée -* `__force_display`, si présente et contient une valeur non nulle, pas de - navigation quelque soit le nombre d'entités à afficher -* `__method`, si le result set à afficher ne contient qu'une entité et que ce - paramètre est spécifié, celui-ci désigne une méthode à appeler sur l'entité - en lui donnant en argument le dictionnaire des paramètres de formulaire, avant - de reprendre le comportement classique (s'insère entre les étapes 1. et - 2. décrites ci-dessus) - - -.. include:: 05-01-views-stdlib.fr.txt - - -Vues xml, binaires... ---------------------- -Pour les vues générants autre que du html (une image générée dynamiquement par -exemple), et qui ne peuvent donc généralement pas être incluse dans la page -HTML générée par le template principal (voir ci-dessus), il faut : - -* placer l'attribut `templatable` de la classe à `False` -* indiquer via l'attribut `content_type` de la classe le type MIME généré par la - vue 'application/octet-stream' - -Pour les vues générants un contenu binaire (une image générée dynamiquement par -exemple), il faut également placer l'attribut `binary` de la classe à `True` (ce -qui implique `templatable == False` afin que l'attribut `w` de la vue soit -remplacé par un flux binaire plutôt que unicode. - - -Quelques trucs (X)HTML à respecter ----------------------------------- -Certains navigateurs (dont firefox) n'aime pas les `
    ` vides (par vide -j'entend sans contenu dans la balise, il peut y avoir des attributs), faut -toujours mettre `
    ` même s'il n'y a rien dedans, et non `
    `. diff -r 972bd504daf6 -r 308037210dab doc/book/fr/06-define-workflows.fr.txt --- a/doc/book/fr/06-define-workflows.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -.. -*- coding: utf-8 -*- - -Définition de workflow -====================== - -Avant-propos ------------- - -Un worflow décrit comment les entités vont être utilisés à travers différents états. Nous avons donc pour un workflow donné un ensemble d'états, un "graphe de transition" c'est-à-dire la liste des transitions possibles d'un état à un autre. - -Nous allons définir ici un simple workflow pour l'exemple du blog avec seulement deux états: `en attente` et `publié`. Il est nécessaire d'avoir préalablement créé une application simple *CubicWeb* en dix minutes (voir :ref:`BlogFiveMinutes`). - -Mise en place du workflow -------------------------- - -Nous allons créer un workflow pour contrôler la qualité des BlogEntry soumis à l'instance. Lorsque un BlogEntry est créé par un utilisateur, son état doit être `en attente`. Pour être visible par tous, il doit être ensuite mis à l'état `publié`. Pour le changement d'état d'`en attente` à `publié`, nous avons besoin d'une transition que nous appellerons `approuve_blogentry`. - -Un état BlogEntry ne doit pas pouvoir être modifiable par les utilisateurs. Nous allons donc créé un groupe de modération `moderateurs` et ce groupe aura les permissions idoines pour publier un BlogEntry. - -Il existe deux manières de créer un workflow: depuis l'interface utilisateur ou en le définissant dans le fichier ``migration/postcreate.py``. -Ce script est exécuté à chaque lancement de la commande ``cubicweb-ctl db-init``. -Nous encourageons vivement la création dans ``migration/postcreate.py`` que nous allons vous montrer ici. Lire `Sous le capot`_ pour en comprendre les raisons. - -L'état d'une entité est sauvegardé par l'attribut `in_state` qui peut être ajouté à votre schéma d'entité par deux façons: - -* héritage direct en utilisant la classe `cubicweb.schema.WorkflowableEntityType` -* par délégation en utilisant `cubicweb.schema.make_worflowable` (utilisable comme un décorateur également) - -Pour notre exemple de BlogEntry, nous devons avoir: - -.. sourcecode:: python - - from cubicweb.schema import WorkflowableEntityType - - class BlogEntry(EntityType, WorkflowableEntityType): - ... - - -Création des états, transitions et les permissions de groupe -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Le script ``postcreate.py`` est exécuté dans un environnement spécial où plusieurs primitives *CubicWeb* peuvent être utilsées. -Elles sont toutes définies dans ``class ServerMigrationHelper``. -Nous allons maintenant voir dans le prochain example celles utilisées pour créer un workflow. - -Pour définir notre workflow pour BlogDemo, veuillez ajouter les lignes suivantes au script ``migration/postcreate.py``: - -.. sourcecode:: python - - _ = unicode - - moderators = add_entity('CWGroup', name=u"modérateurs") - -Cela va ajouter le groupe utilisateur `moderators`. - -.. sourcecode:: python - - wf = add_workflow(u'une description succincte de votre workflow', 'BlogEntry') - -Ceci va premièrement instancier un nouvel objet workflow avec une description sommaire mais pertinente et le type d'entité concerné (un tuple pour être utilisé pour des valeurs multiples). - -.. sourcecode:: python - - submitted = wf.add_state(_('submitted'), initial=True) - published = wf.add_state(_('published')) - -``add_state`` attend comme premier argument le nom de l'état que vous voulez créer et un argument optionnel pour signifier si c'est l'état initial supposé pour ce type d'entité. - -.. sourcecode:: python - - wf.add_transition(_('approuve_blogentry'), (submitted,), published, ('moderators', 'managers'),) - -``add_transition`` attend: - - * comme premier argument le nom de la transition - * ensuite la liste des états pour lesquels les transitions peuvent être tirées, - * l'état attendu en fin de transition, - * et les permissions - (c'est-à-dire la liste des goupes utilisateurs qui peuvnet appliquer la transition; l'utilisateur devant appartenir à l'un des groupes listés pour être autoriser à exécuter l'action). - -.. sourcecode:: python - - - checkpoint() - -.. note:: - Dans le script de création d'un workflow, penser à mettre `_()` autour des noms d'états et de transitions pour que ceux si soient pris en compte par les scripts de gestion des catalogues i18n. - -En complément de condition sur des groupes utilisateur dont l'utilisateur doit appartenir à l'in d'entre eux, vous pouvez utiliser une RQL condition. -Dans ce cas, l'utilisateur peut seulement exécuter une action si les deux conditions sont satisfaites. - -Pour la condition RQL sur une transition, on peut y mettre les substitutions suivantes : - -* `%(eid)s`, eid de l'objet -* `%(ueid)s`, eid de l'utilisateur qui fait la requête -* `%(seid)s`, eid de l'état courant de l'objet - -.. image:: ../../images/03-transitions-view.en.png - -Vous pouvez remarqué que dans la boîte d'action d'un BlogEntry, l'état est maintenant listé ainsi que les possibles transitions définis pour l'état en cours dans le workflow. Les transitions ne sont seulement affichées pour les utilisateurs ayant les bonnes permissions. -Dans notre exemple, la transition `approuve_blogentry` sera seulement affichée pour les utilisateurs appartenant au groupe `moderators` or `managers`. - - -Sous le capot -~~~~~~~~~~~~~ - -Un workflow est une collection d'entités de type `State`` et ``Transition`` qui sont des types d'entités standards de *CubicWeb*. - -Par exemple, les lignes précédentes: - -.. sourcecode:: python - - submitted = wf.add_state(_('en attente'), initial=True) - published = wf.add_state(_('publié')) - -vont créé deux entités de type ``State``, l'une avec le nom 'submitted' et l'autre avec le nom 'published'. Tandis que: - -.. sourcecode:: python - - wf.add_transition(_('approuve_blogentry'), (submitted,), published, ('moderators', 'managers'),) - -va créé une entité de type ``Transition`` avec le nom `approuve_blogentry` qui sera relié aux entités ``State`` créées précédemment. - -Dès lors, nous pouvons utiliser l'interface d'administration pour ces opérations. Mais ce n'est pas recommandé à cause de la complexité superflue et du fait que ces changements ne seront locaux qu'à cette instance. - -En effet, si vous créez les états et les transitions à travers l'interface utilisateur, la prochaine initialisation de la base de données vous oblige à recréer toutes les entités. -L'interface utilisateur devrait être seulement connu par vous pour la visualisation des états et transitions, mais ce n'est pas celle appropriée pour définir vos workflows applicatifs. - - diff -r 972bd504daf6 -r 308037210dab doc/book/fr/07-01-define-entities.fr.txt --- a/doc/book/fr/07-01-define-entities.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -.. -*- coding: utf-8 -*- - -Paramétrages et extensions spécifiques --------------------------------------- - -Valeurs par défaut dynamiques -````````````````````````````` -Il est possible de définir dans le schéma des valeurs par défaut *statiques*. -Il est également possible de définir des valeurs par défaut *dynamiques* en -définissant sur la classe d'entité une méthode `default_` pour -un attribut donnée. - - -Contrôle des attributs chargés et du tri par défaut -``````````````````````````````````````````````````` -* l'attribut de classe `fetch_attrs` permet de définir sur une classe d'entité - la liste des noms des attributs ou relations devant être chargés - automatiquement lors de la récupération d'entité(s) de ce type. Dans le cas - des relations, on est limité aux relations *sujets de cardinalité `?` ou `1`*. - -* la méthode de classe `fetch_order(attr, var)` prend en argument un nom - d'attribut (ou de relation) et un nom de variable et doit retourner une chaine - à utiliser dans la close "ORDERBY" d'une requête RQL pour trier - automatiquement les listes d'entités de ce type selon cet attribut, ou `None` - si l'on ne veut pas de tri sur l'attribut passé en argument. Par défaut les - entités sont triées selon leur date de création - -* la méthode de classe `fetch_unrelated_order(attr, var)` est similaire à la - méthode `fetch_order` mais est utilisée essentiellement pour contrôler le tri - des listes déroulantes permettant de créer des relations dans la vue d'édition - d'une entité - -La fonction `fetch_config(fetchattrs, mainattr=None)` permet de simplifier la -définition des attributs à précharger et du tri en retournant une liste des -attributs à précharger (en considérant ceux de la classe `AnyEntity` -automatiquement) et une fonction de tri sur l'attribut "principal" (le 2eme -argument si spécifié ou sinon le premier attribut de la liste `fetchattrs`). -Cette fonction est définie dans le package `ginco.entities`. - -Par exemple : :: - - class Transition(AnyEntity): - """...""" - id = 'Transition' - fetch_attrs, fetch_order = fetch_config(['name']) - -Indique que pour le type d'entité "Transition" il faut précharger l'attribut -"name" et trier par défaut selon cet attribut. - - -Contrôle des formulaires d'édition -`````````````````````````````````` -Il est possible de contrôler les attributs/relations dans la vue d'édition -simple ou multiple à l'aide des *rtags* suivants : - -* `primary`, indique qu'un attribut ou une relation doit être incorporé dans - les formulaires d'édition simple et multiple. Dans le cas d'une relation, - le formulaire d'édition de l'entité liée sera inclus dans le formulaire - -* `secondary`, indique qu'un attribut ou une relation doit être incorporé dans - le formulaire d'édition simple uniquement. Dans le cas d'une relation, - le formulaire d'édition de l'entité liée sera inclus dans le formulaire - -* `generic`, indique qu'une relation doit être incorporé dans le formulaire - d'édition simple dans la boite générique d'ajout de relation - -* `generated`, indique qu'un attribut est caculé dynamiquement ou autre, et - qu'il ne doit donc pas être présent dans les formulaires d'édition - -Au besoin il est possible de surcharger la méthode -`relation_category(rtype, x='subject')` pour calculer dynamiquement la catégorie -d'édition d'une relation. - - -Contrôle de la boîte "add_related" -`````````````````````````````````` -La boite `add related` est une boite automatique proposant de créer une entité -qui sera automatiquement liée à l'entité de départ (le contexte dans lequel -s'affiche la boite). Par défaut, les liens présents dans cette boite sont -calculés en fonction des propriétés du schéma de l'entité visualisée, mais il -est possible de les spécifier explicitement à l'aide des *rtags* suivants : - -* `link`, indique qu'une relation est généralement créée vers une entité - existante et qu'il ne faut donc pas faire apparaitre de lien pour cette - relation - -* `create`, indique qu'une relation est généralement créée vers de nouvelles - entités et qu'il faut donc faire apparaitre un lien pour créer une nouvelle - entité et la lier automatiquement - -Au besoin il est possible de surcharger la méthode -`relation_mode(rtype, targettype, x='subject')` pour caculer dynamiquement la -catégorie de création d'une relation. - -A noter également que si au moins une action dans la catégorie "addrelated" est -trouvée pour le contexte courant, le fonctionnement automatique est désactivé -en faveur du fonctionnement explicite (i.e. affichage des actions de la -catégorie "addrelated" uniquement). - -Contrôle des formulaires de filtrage de table -````````````````````````````````````````````` -La vue "table" par défaut gère dynamiquement un formulaire de filtrage du -contenu de celle-ci. L'algorithme est le suivant : - -1. on considère que la première colonne contient les entités à restreindre -2. on recupère la première entité de la table (ligne 0) pour "représenter" - toutes les autres -3. pour toutes les autres variables définies dans la requête originale : - - 1. si la variable est liée à la variable principale par au moins une - n'importe quelle relation - 2. on appelle la méthode `filterform_vocabulary(rtype, x)` sur l'entité - et si rien est retourné (ou plus exactement un tuple de valeur `None`, - voir ci-dessous) on passe à la variable suivante, sinon un élément de - formulaire de filtrage sera créé avec les valeurs de vocabulaire - retournées - -4. il n'y a pas d'autres limitations sur le rql, il peut comporter des clauses - de tris, de groupes... Des fonctions javascripts sont utilisées pour - regénérer une requête à partir de la requête de départ et des valeurs - séléctionnées dans les filtres de formulaire. - - -La méthode `filterform_vocabulary(rtype, x, var, rqlst, args, cachekey)` prend -en argument le nom d'une relation et la "cible", qui indique si l'entité sur -laquelle la méthode est appellée est sujet ou objet de la relation. Elle doit -retourner : - -* un 2-uple de None si elle ne sait pas gérer cette relation - -* un type et une liste contenant le vocabulaire - - * la liste doit contenir des couples (valeur, label) - * le type indique si la valeur désigne un nombre entier (`type == 'int'`), une - chaîne de caractères (`type == 'string'`) ou une entité non finale (`type - == 'eid'`) - -Par exemple dans notre application de gestion de tickets, on veut pouvoir -filtrés ceux-ci par : - -* type -* priorité -* état (in_state) -* étiquette (tags) -* version (done_in) - -On définit donc la méthode suivante : :: - - - class Ticket(AnyEntity): - - ... - - def filterform_vocabulary(self, rtype, x, var, rqlst, args, cachekey): - _ = self.req._ - if rtype == 'type': - return 'string', [(x, _(x)) for x in ('bug', 'story')] - if rtype == 'priority': - return 'string', [(x, _(x)) for x in ('minor', 'normal', 'important')] - if rtype == 'done_in': - rql = insert_attr_select_relation(rqlst, var, rtype, 'num') - return 'eid', self.req.execute(rql, args, cachekey) - return super(Ticket, self).filterform_vocabulary(rtype, x, var, rqlst, - args, cachekey) - - -NOTE: Le support du filtrage sur les étiquettes et l'état est installé -automatiquement, pas besoin de le gérer ici. diff -r 972bd504daf6 -r 308037210dab doc/book/fr/07-data-as-objects.fr.txt --- a/doc/book/fr/07-data-as-objects.fr.txt Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -.. -*- coding: utf-8 -*- - - -Manipulation des données stockées -================================= - -Les classes `Entity` et `AnyEntity` ------------------------------------ -Pour fournir un comportement spécifique à un type d'entité, il suffit de définir -une classe héritant de la class `ginco.entities.AnyEntity`. En général il faut -définir ces classes dans un module du package `entities` d'une application pour -qu'elle soit disponible à la fois coté serveur et coté client. - -La classe `AnyEntity` est une classe chargée dynamiquement héritant de la classe -de base `Entity` (`ginco.common.entity`). On définit une sous-classe pour -ajouter des méthodes ou spécialiser les comportements d'un type d'entité donné. - -Des descripteurs sont ajoutés à l'enregistrement pour initialiser la classe en -fonction du schéma : - -* on peut accéder aux attributs définis dans le schéma via les attributs de même - nom sur les instances (valeur typée) - -* on peut accéder aux relations définies dans le schéma via les attributs de même - nom sur les instances (liste d'instances d'entité) - -Les méthodes définies sur la classe `AnyEntity` ou `Entity` sont les suivantes : - -* `has_eid()`, retourne vrai si l'entité à un eid affecté (i.e. pas en cours de - création) - -* `check_perm(action)`, vérifie que l'utilisateur à le droit d'effectuer - l'action demandée sur l'entité - -:Formattage et génération de la sortie: - - * `view(vid, **kwargs)`, applique la vue donnée à l'entité - - * `absolute_url(**kwargs)`, retourne une URL absolue permettant d'accéder à la - vue primaire d'une entité - - * `rest_path()`, renvoie une l'URL REST relative permettant d'obtenir l'entité - - * `format(attr)`, retourne le format (type MIME) du champ passé en argument - - * `printable_value(attr, value=_marker, attrtype=None, format='text/html')`, - retourne une chaine permettant l'affichage dans un format donné de la valeur - d'un attribut (la valeur est automatiquement récupérée au besoin) - - * `display_name(form='')`, retourne une chaîne pour afficher le type de - l'entité, en spécifiant éventuellement la forme désirée ('plural' pour la - forme plurielle) - -:Gestion de données: - - * `as_rset()`, transforme l'entité en un resultset équivalent simulant - le résultat de la requête `Any X WHERE X eid _eid_` - - * `complete(skip_bytes=True)`, effectue une requête permettant de récupérer d'un - coup toutes les valeurs d'attributs manquant sur l'entité - - * `get_value(name)`, récupere la valeur associée à l'attribut passé en argument - - * `related(rtype, x='subject', limit=None, entities=False)`, retourne une liste - des entités liées à l'entité courant par la relation donnée en argument - - * `unrelated(rtype, targettype, x='subject', limit=None)`, retourne un result set - des entités not liées à l'entité courante par la relation donnée en argument - et satisfaisants les contraintes de celle-ci - - * `set_attributes(**kwargs)`, met à jour la liste des attributs avec - les valeurs correspondantes passées sous forme d'arguments nommés - - * `copy_relations(ceid)`, copie les relations de l'entité ayant l'eid passé en - argument sur l'entité courante - - * `last_modified(view)`, retourne la date à laquelle on doit considérer - l'objet comme modifié (utiliser par la gestion de cache HTTP) - - * `delete()` permet de supprimer l'entité représentée - -:Meta-données standard (Dublin Core): - - * `dc_title()`, retourne une chaine unicode correspondant à la méta-donnée - 'Title' (utilise par défaut le premier attribut non 'meta' du schéma de - l'entité) - - * `dc_long_title()`, comme dc_title mais peut retourner un titre plus détaillé - - * `dc_description(format='text/plain')`, retourne une chaine unicode - correspondant à la méta-donnée 'Description' (cherche un attribut - 'description' par défaut) - - * `dc_authors()`, retourne une chaine unicode correspondant à la méta-donnée - 'Authors' (propriétaires par défaut) - - * `dc_date(date_format=None)`, retourne une chaine unicode - correspondant à la méta-donnée 'Date' (date de modification par défaut) - -:Contrôle du vocabulaire pour les relations: - - * `vocabulary(rtype, x='subject', limit=None)`, appelée notamment - par les vues d'édition d'erudi, elle renvoie une liste de couple - (label, eid) des entités qui pourraient être liées à l'entité - via la relation `rtype` - * `subject_relation_vocabulary(rtype, limit=None)`, appelée - en interne par `vocabulary` dans le cas d'une relation sujet - * `object_relation_vocabulary(rtype, limit=None)`, appelée - en interne par `vocabulary` dans le cas d'une relation objet - * `relation_vocabulary(rtype, targettype, x, limit=None)`, appelé - en interne par `subject_relation_vocabulary` et `object_relation_vocabulary` - - -Les *rtags* ------------ -Les *rtags* permettent de spécifier certains comportements propres aux relations -d'un type d'entité donné (voir plus loin). Ils sont définis sur la classe -d'entité via l'attribut `rtags` qui est un dictionnaire dont les clés sont un -triplet :: - - , ,
    ') - for rentity in related.entities(): - # for each related entity, provide a link to remove the relation - subview = rentity.view(self.item_vid) - if maydel: - jscall = unicode(js.ajaxBoxRemoveLinkedEntity( - self.__regid__, entity.eid, rentity.eid, - self.fname_remove, - self.removed_msg and _(self.removed_msg))) - w(u'' - '' % (xml_escape(jscall), - subview)) - else: - w(u'' % (subview)) - w(u'
    [-]%s
    %s
    ') - else: - w(_('no related entity')) - if mayadd: - req.add_js('jquery.autocomplete.js') - req.add_css('jquery.autocomplete.css') - multiple = rdef.role_cardinality(self.role) in '*+' - w(u'
    ') - jscall = unicode(js.ajaxBoxShowSelector( - self.__regid__, entity.eid, self.fname_vocabulary, - self.fname_validate, self.added_msg and _(self.added_msg), - _(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]), - multiple and self.separator)) - w('%s' % ( - xml_escape(jscall), - multiple and _('add_relation') or _('update_relation'))) - w(u'') - w(u'
    ' % divid) - w(u'
    ') - w(u'\n') - w(u'\n') diff -r 972bd504daf6 -r 308037210dab web/component.py --- a/web/component.py Mon Oct 18 11:04:19 2010 +0200 +++ b/web/component.py Mon Oct 18 11:47:06 2010 +0200 @@ -22,58 +22,24 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.common.deprecation import class_renamed +from warnings import warn + +from logilab.common.deprecation import class_deprecated, class_renamed from logilab.mtconverter import xml_escape -from cubicweb import role +from cubicweb import Unauthorized, role, target, tags +from cubicweb.schema import display_name +from cubicweb.uilib import js, domid from cubicweb.utils import json_dumps -from cubicweb.uilib import js -from cubicweb.view import Component -from cubicweb.selectors import ( - paginated_rset, one_line_rset, primary_view, match_context_prop, - partial_has_related_entities) +from cubicweb.view import ReloadableMixIn, Component +from cubicweb.selectors import (no_cnx, paginated_rset, one_line_rset, + non_final_entity, partial_relation_possible, + partial_has_related_entities) +from cubicweb.appobject import AppObject +from cubicweb.web import INTERNAL_FIELD_VALUE, htmlwidgets, stdmsgs -class EntityVComponent(Component): - """abstract base class for additinal components displayed in content - headers and footer according to: - - * the displayed entity's type - * a context (currently 'header' or 'footer') - - it should be configured using .accepts, .etype, .rtype, .target and - .context class attributes - """ - - __registry__ = 'contentnavigation' - __select__ = one_line_rset() & primary_view() & match_context_prop() - - cw_property_defs = { - _('visible'): dict(type='Boolean', default=True, - help=_('display the component or not')), - _('order'): dict(type='Int', default=99, - help=_('display order of the component')), - _('context'): dict(type='String', default='navtop', - vocabulary=(_('navtop'), _('navbottom'), - _('navcontenttop'), _('navcontentbottom'), - _('ctxtoolbar')), - help=_('context where this component should be displayed')), - } - - context = 'navcontentbottom' - - def call(self, view=None): - if self.cw_rset is None: - self.entity_call(self.cw_extra_kwargs.pop('entity')) - else: - self.cell_call(0, 0, view=view) - - def cell_call(self, row, col, view=None): - self.entity_call(self.cw_rset.get_entity(row, col), view=view) - - def entity_call(self, entity, view=None): - raise NotImplementedError() - +# abstract base class for navigation components ################################ class NavigationComponent(Component): """abstract base class for navigation components""" @@ -183,6 +149,430 @@ return self.next_page_link_templ % (url, title, content) +# new contextual components system ############################################# + +def override_ctx(cls, **kwargs): + cwpdefs = cls.cw_property_defs.copy() + cwpdefs['context'] = cwpdefs['context'].copy() + cwpdefs['context'].update(kwargs) + return cwpdefs + + +class EmptyComponent(Exception): + """some selectable component has actually no content and should not be + rendered + """ + +class Layout(Component): + __regid__ = 'layout' + __abstract__ = True + + def init_rendering(self): + """init view for rendering. Return true if we should go on, false + if we should stop now. + """ + view = self.cw_extra_kwargs['view'] + try: + view.init_rendering() + except Unauthorized, ex: + self.warning("can't render %s: %s", view, ex) + return False + except EmptyComponent: + return False + return True + + +class CtxComponent(AppObject): + """base class for contextual compontents. The following contexts are + predefined: + + * boxes: 'left', 'incontext', 'right' + * section: 'navcontenttop', 'navcontentbottom', 'navtop', 'navbottom' + * other: 'ctxtoolbar' + + The 'incontext', 'navcontenttop', 'navcontentbottom' and 'ctxtoolbar' + context are handled by the default primary view, others by the default main + template. + + All subclasses may not support all those contexts (for instance if it can't + be displayed as box, or as a toolbar icon). You may restrict allowed context + as followed: + + .. sourcecode:: python + + class MyComponent(CtxComponent): + cw_property_defs = override_ctx(CtxComponent, + vocabulary=[list of contexts]) + context = 'my default context' + + You can configure default component's context by simply giving appropriate + value to the `context` class attribute, as seen above. + """ + __registry__ = 'ctxcomponents' + __select__ = ~no_cnx() + + categories_in_order = () + cw_property_defs = { + _('visible'): dict(type='Boolean', default=True, + help=_('display the box or not')), + _('order'): dict(type='Int', default=99, + help=_('display order of the box')), + _('context'): dict(type='String', default='left', + vocabulary=(_('left'), _('incontext'), _('right'), + _('navtop'), _('navbottom'), + _('navcontenttop'), _('navcontentbottom'), + _('ctxtoolbar')), + help=_('context where this component should be displayed')), + } + visible = True + order = 0 + context = 'left' + contextual = False + title = None + + # XXX support kwargs for compat with old boxes which gets the view as + # argument + def render(self, w, **kwargs): + if hasattr(self, 'call'): + warn('[3.10] should not anymore implements call on %s, see new CtxComponent api' + % self.__class__, DeprecationWarning) + self.w = w + def wview(__vid, rset=None, __fallback_vid=None, **kwargs): + self._cw.view(__vid, rset, __fallback_vid, w=self.w, **kwargs) + self.wview = wview + self.call(**kwargs) + return + getlayout = self._cw.vreg['components'].select + try: + # XXX ensure context is given when the component is reloaded through + # ajax + context = self.cw_extra_kwargs['context'] + except KeyError: + context = self.cw_propval('context') + layout = getlayout('layout', self._cw, rset=self.cw_rset, + row=self.cw_row, col=self.cw_col, + view=self, context=context) + layout.render(w) + + def init_rendering(self): + """init rendering callback: that's the good time to check your component + has some content to display. If not, you can still raise + :exc:`EmptyComponent` to inform it should be skipped. + + Also, :exc:`Unauthorized` will be catched, logged, then the component + will be skipped. + """ + self.items = [] + + @property + def domid(self): + """return the HTML DOM identifier for this component""" + return domid(self.__regid__) + + @property + def cssclass(self): + """return the CSS class name for this component""" + return domid(self.__regid__) + + def render_title(self, w): + """return the title for this component""" + if self.title: + w(self._cw._(self.title)) + + def render_body(self, w): + """return the body (content) for this component""" + raise NotImplementedError() + + def render_items(self, w, items=None, klass=u'boxListing'): + if items is None: + items = self.items + assert items + w(u'
      ' % klass) + for item in items: + if hasattr(item, 'render'): + item.render(w) # XXX display
    • by itself + else: + w(u'
    • ') + w(item) + w(u'
    • ') + w(u'
    ') + + def append(self, item): + self.items.append(item) + + def box_action(self, action): # XXX action_link + return self.build_link(self._cw._(action.title), action.url()) + + def build_link(self, title, url, **kwargs): + if self._cw.selected(url): + try: + kwargs['klass'] += ' selected' + except KeyError: + kwargs['klass'] = 'selected' + return tags.a(title, href=url, **kwargs) + + +class EntityCtxComponent(CtxComponent): + """base class for boxes related to a single entity""" + __select__ = CtxComponent.__select__ & non_final_entity() & one_line_rset() + context = 'incontext' + contextual = True + + def __init__(self, *args, **kwargs): + super(EntityCtxComponent, self).__init__(*args, **kwargs) + try: + entity = kwargs['entity'] + except KeyError: + entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0) + self.entity = entity + + @property + def domid(self): + return domid(self.__regid__) + unicode(self.entity.eid) + + +# high level abstract classes ################################################## + +class RQLCtxComponent(CtxComponent): + """abstract box for boxes displaying the content of a rql query not + related to the current result set. + """ + rql = None + + def to_display_rql(self): + assert self.rql is not None, self.__regid__ + return (self.rql,) + + def init_rendering(self): + rset = self._cw.execute(*self.to_display_rql()) + if not rset: + raise EmptyComponent() + if len(rset[0]) == 2: + self.items = [] + for i, (eid, label) in enumerate(rset): + entity = rset.get_entity(i, 0) + self.items.append(self.build_link(label, entity.absolute_url())) + else: + self.items = [self.build_link(e.dc_title(), e.absolute_url()) + for e in rset.entities()] + + def render_body(self, w): + self.render_items(w) + + +class EditRelationMixIn(ReloadableMixIn): + def box_item(self, entity, etarget, rql, label): + """builds HTML link to edit relation between `entity` and `etarget`""" + args = {role(self)[0] : entity.eid, target(self)[0] : etarget.eid} + url = self._cw.user_rql_callback((rql, args)) + # for each target, provide a link to edit the relation + return u'[%s] %s' % (xml_escape(url), label, + etarget.view('incontext')) + + def related_boxitems(self, entity): + rql = 'DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype + return [self.box_item(entity, etarget, rql, u'-') + for etarget in self.related_entities(entity)] + + def related_entities(self, entity): + return entity.related(self.rtype, role(self), entities=True) + + def unrelated_boxitems(self, entity): + rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype + return [self.box_item(entity, etarget, rql, u'+') + for etarget in self.unrelated_entities(entity)] + + def unrelated_entities(self, entity): + """returns the list of unrelated entities, using the entity's + appropriate vocabulary function + """ + skip = set(unicode(e.eid) for e in entity.related(self.rtype, role(self), + entities=True)) + skip.add(None) + skip.add(INTERNAL_FIELD_VALUE) + filteretype = getattr(self, 'etype', None) + entities = [] + form = self._cw.vreg['forms'].select('edition', self._cw, + rset=self.cw_rset, + row=self.cw_row or 0) + field = form.field_by_name(self.rtype, role(self), entity.e_schema) + for _, eid in field.vocabulary(form): + if eid not in skip: + entity = self._cw.entity_from_eid(eid) + if filteretype is None or entity.__regid__ == filteretype: + entities.append(entity) + return entities + + +class EditRelationCtxComponent(EditRelationMixIn, EntityCtxComponent): + """base class for boxes which let add or remove entities linked by a given + relation + + subclasses should define at least id, rtype and target class attributes. + """ + def render_title(self, w): + return display_name(self._cw, self.rtype, role(self), + context=self.entity.__regid__) + + def render_body(self, w): + self._cw.add_js('cubicweb.ajax.js') + related = self.related_boxitems(self.entity) + unrelated = self.unrelated_boxitems(self.entity) + self.items.extend(related) + if related and unrelated: + self.items.append(htmlwidgets.BoxSeparator()) + self.items.extend(unrelated) + self.render_items(w) + + +class AjaxEditRelationCtxComponent(EntityCtxComponent): + __select__ = EntityCtxComponent.__select__ & ( + partial_relation_possible(action='add') | partial_has_related_entities()) + + # view used to display related entties + item_vid = 'incontext' + # values separator when multiple values are allowed + separator = ',' + # msgid of the message to display when some new relation has been added/removed + added_msg = None + removed_msg = None + + # class attributes below *must* be set in concret classes (additionaly to + # rtype / role [/ target_etype]. They should correspond to js_* methods on + # the json controller + + # function(eid) + # -> expected to return a list of values to display as input selector + # vocabulary + fname_vocabulary = None + + # function(eid, value) + # -> handle the selector's input (eg create necessary entities and/or + # relations). If the relation is multiple, you'll get a list of value, else + # a single string value. + fname_validate = None + + # function(eid, linked entity eid) + # -> remove the relation + fname_remove = None + + def __init__(self, *args, **kwargs): + super(AjaxEditRelationCtxComponent, self).__init__(*args, **kwargs) + self.rdef = self.entity.e_schema.rdef(self.rtype, self.role, self.target_etype) + + def render_title(self, w): + w(self.rdef.rtype.display_name(self._cw, self.role, + context=self.entity.__regid__)) + + def render_body(self, w): + req = self._cw + entity = self.entity + related = entity.related(self.rtype, self.role) + if self.role == 'subject': + mayadd = self.rdef.has_perm(req, 'add', fromeid=entity.eid) + maydel = self.rdef.has_perm(req, 'delete', fromeid=entity.eid) + else: + mayadd = self.rdef.has_perm(req, 'add', toeid=entity.eid) + maydel = self.rdef.has_perm(req, 'delete', toeid=entity.eid) + if mayadd or maydel: + req.add_js(('jquery.ui.js', 'cubicweb.widgets.js')) + req.add_js(('cubicweb.ajax.js', 'cubicweb.ajax.box.js')) + _ = req._ + if related: + w(u'') + for rentity in related.entities(): + # for each related entity, provide a link to remove the relation + subview = rentity.view(self.item_vid) + if maydel: + jscall = unicode(js.ajaxBoxRemoveLinkedEntity( + self.__regid__, entity.eid, rentity.eid, + self.fname_remove, + self.removed_msg and _(self.removed_msg))) + w(u'' + '' % (xml_escape(jscall), + subview)) + else: + w(u'' % (subview)) + w(u'
    %s
    %s
    ') + else: + w(_('no related entity')) + if mayadd: + req.add_js(('jquery.ui.js', 'cubicweb.widgets.js')) + req.add_css('jquery.ui.css') + multiple = self.rdef.role_cardinality(self.role) in '*+' + w(u'
    ') + jscall = unicode(js.ajaxBoxShowSelector( + self.__regid__, entity.eid, self.fname_vocabulary, + self.fname_validate, self.added_msg and _(self.added_msg), + _(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]), + multiple and self.separator)) + w('%s' % ( + xml_escape(jscall), + multiple and _('add_relation') or _('update_relation'))) + w(u'') + w(u'
    ' % self.domid) + w(u'
    ') + + +class RelatedObjectsCtxComponent(EntityCtxComponent): + """a contextual component to display entities related to another""" + __select__ = EntityCtxComponent.__select__ & partial_has_related_entities() + context = 'navcontentbottom' + rtype = None + role = 'subject' + + vid = 'list' + + def render_body(self, w): + rset = self.entity.related(self.rtype, role(self)) + self._cw.view(self.vid, rset, w=w) + + +# old contextual components, deprecated ######################################## + +class EntityVComponent(Component): + """abstract base class for additinal components displayed in content + headers and footer according to: + + * the displayed entity's type + * a context (currently 'header' or 'footer') + + it should be configured using .accepts, .etype, .rtype, .target and + .context class attributes + """ + __metaclass__ = class_deprecated + __deprecation_warning__ = '[3.10] *VComponent classes are deprecated, use *CtxComponent instead (%(cls)s)' + + __registry__ = 'ctxcomponents' + __select__ = one_line_rset() + + cw_property_defs = { + _('visible'): dict(type='Boolean', default=True, + help=_('display the component or not')), + _('order'): dict(type='Int', default=99, + help=_('display order of the component')), + _('context'): dict(type='String', default='navtop', + vocabulary=(_('navtop'), _('navbottom'), + _('navcontenttop'), _('navcontentbottom'), + _('ctxtoolbar')), + help=_('context where this component should be displayed')), + } + + context = 'navcontentbottom' + + def call(self, view=None): + if self.cw_rset is None: + self.entity_call(self.cw_extra_kwargs.pop('entity')) + else: + self.cell_call(0, 0, view=view) + + def cell_call(self, row, col, view=None): + self.entity_call(self.cw_rset.get_entity(row, col), view=view) + + def entity_call(self, entity, view=None): + raise NotImplementedError() + + class RelatedObjectsVComponent(EntityVComponent): """a section to display some related entities""" __select__ = EntityVComponent.__select__ & partial_has_related_entities() @@ -203,14 +593,15 @@ rset = self._cw.execute(self.rql(), {'x': eid}) if not rset.rowcount: return - self.w(u'
    ' % self.div_class()) + self.w(u'
    ' % self.cssclass) self.w(u'

    %s

    \n' % self._cw._(self.title).capitalize()) self.wview(self.vid, rset) self.w(u'
    ') + VComponent = class_renamed('VComponent', Component, - 'VComponent is deprecated, use Component') + '[3.2] VComponent is deprecated, use Component') SingletonVComponent = class_renamed('SingletonVComponent', Component, - 'SingletonVComponent is deprecated, use ' + '[3.2] SingletonVComponent is deprecated, use ' 'Component and explicit registration control') diff -r 972bd504daf6 -r 308037210dab web/data/actionBoxHeader.png Binary file web/data/actionBoxHeader.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/boxHeader.png Binary file web/data/boxHeader.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/contextFreeBoxHeader.png Binary file web/data/contextFreeBoxHeader.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/contextualBoxHeader.png Binary file web/data/contextualBoxHeader.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.ajax.box.js --- a/web/data/cubicweb.ajax.box.js Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.ajax.box.js Mon Oct 18 11:47:06 2010 +0200 @@ -53,28 +53,23 @@ deferred.addCallback(function (unrelated) { var input = INPUT({'type': 'text', 'id': inputid, 'size': 20}); holder.append(input).show(); - $input = $(input); - $input.keypress(function (event) { - if (event.keyCode == KEYS.KEY_ENTER) { - // XXX not very user friendly: we should test that the suggestions - // aren't visible anymore + var $input = $(input); + $input.keypress(function (evt) { + if (evt.keyCode == $.ui.keyCode.ENTER) { ajaxBoxValidateSelectorInput(boxid, eid, separator, addfname, msg); } }); + $input.cwautocomplete(unrelated, {multiple: true}); var buttons = DIV({'class' : "sgformbuttons"}, - A({'href' : "javascript: noop();", - 'onclick' : cw.utils.strFuncCall('ajaxBoxValidateSelectorInput', + A({href : "javascript: noop();", + onclick : cw.utils.strFuncCall('ajaxBoxValidateSelectorInput', boxid, eid, separator, addfname, msg)}, - oklabel), + oklabel), ' / ', A({'href' : "javascript: noop();", 'onclick' : '$("#' + holderid + '").empty()'}, cancellabel)); holder.append(buttons); - $input.autocomplete(unrelated, { - multiple: separator, - max: 15 - }); $input.focus(); }); } diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.ajax.js Mon Oct 18 11:47:06 2010 +0200 @@ -188,7 +188,7 @@ _loadDynamicFragments(node); // XXX [3.7] jQuery.one is now used instead jQuery.bind, // jquery.treeview.js can be unpatched accordingly. - jQuery(CubicWeb).trigger('server-response', [true, node]); + jQuery(cw).trigger('server-response', [true, node]); jQuery(node).trigger('server-response', [true, node]); } @@ -296,6 +296,7 @@ url: url, type: (reqtype || 'GET').toUpperCase(), data: form, + traditional: true, async: true, beforeSend: function(xhr) { @@ -303,9 +304,6 @@ }, success: function(data, status) { - if (deferred._req.getResponseHeader("content-type") == 'application/json') { - data = cw.evalJSON(data); - } deferred.success(data); }, @@ -328,14 +326,9 @@ url: url, type: (reqtype || 'GET').toUpperCase(), data: form, + traditional: true, async: false }); - // check result.responseText instead of result to avoid error encountered with IE - if (result.responseText) { - // XXX no good reason to force json here, - // it should depends on request content-type - result = cw.evalJSON(result.responseText); - } return result; } } @@ -639,14 +632,15 @@ function(fname /* ... */) { setProgressCursor(); var props = { - 'fname': fname, - 'pageid': pageid, - 'arg': $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) + fname: fname, + pageid: pageid, + arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) }; var result = jQuery.ajax({ url: JSON_BASE_URL, data: props, - async: false + async: false, + traditional: true }).responseText; if (result) { result = cw.evalJSON(result); @@ -661,9 +655,9 @@ function(fname /* ... */) { setProgressCursor(); var props = { - 'fname': fname, - 'pageid': pageid, - 'arg': $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) + fname: fname, + pageid: pageid, + arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) }; // XXX we should inline the content of loadRemote here var deferred = loadRemote(JSON_BASE_URL, props, 'POST'); diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.calendar.css --- a/web/data/cubicweb.calendar.css Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.calendar.css Mon Oct 18 11:47:06 2010 +0200 @@ -230,7 +230,7 @@ .calendar th.month { font-weight:bold; padding-bottom:0.2em; - background: %(actionBoxTitleBgColor)s; + background: %(incontextBoxBodyBgColor)s; } .calendar th.month a{ diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.css --- a/web/data/cubicweb.css Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.css Mon Oct 18 11:47:06 2010 +0200 @@ -31,19 +31,22 @@ /* h3 { font-size:1.30769em; } */ /* scale traditional */ -h1 { font-size: %(h1FontSize)s; } +h1, +.vtitle { font-size: %(h1FontSize)s; } h2 { font-size: %(h2FontSize)s; } h3 { font-size: %(h3FontSize)s; } /* paddings */ -h1 { +h1, +.vtitle { border-bottom: %(h1BorderBottomStyle)s; padding: %(h1Padding)s; margin: %(h1Margin)s; color: %(h1Color)s; } -div.tabbedprimary + h1, h1.plain { +div.tabbedprimary + h1, +h1.plain { border-bottom: none; } @@ -100,7 +103,7 @@ } ol ol, -ul ul{ +ul ul { margin-left: 8px; margin-bottom : 0px; } @@ -113,7 +116,7 @@ margin-left: 1.5em; } -img{ +img { border: none; } @@ -139,7 +142,7 @@ border: 1px inset %(headerBgColor)s; } -hr{ +hr { border: none; border-bottom: 1px solid %(defaultColor)s; height: 1px; @@ -219,6 +222,7 @@ table#header { background: %(headerBgColor)s url("banner.png") repeat-x top left; text-align: left; + width: 100%; } table#header td { @@ -229,6 +233,15 @@ color: %(defaultColor)s; } +table#header td#headtext { + float: left; +} + +table#header td#header-right { + padding-top: 1em; + float: right; +} + table#header img#logo{ vertical-align: middle; } @@ -239,23 +252,14 @@ white-space: nowrap; } -table#header td#headtext { - width: 100%; -} - /* Popup on login box and userActionBox */ div.popupWrapper { position: relative; - z-index: 100; } div.popup { position: absolute; background: #fff; - /* background-color: #f0eff0; */ - /* background-image: url(popup.png); */ - /* background-repeat: repeat-x; */ - /* background-positon: top left; */ border: 1px solid %(listingBorderColor)s; border-top: none; text-align: left; @@ -273,12 +277,13 @@ margin: %(defaultLayoutMargin)s; } -table#mainLayout #navColumnLeft { +table#mainLayout td#navColumnLeft { width: 16em; padding-right: %(defaultLayoutMargin)s; + } -table#mainLayout #navColumnRight { +table#mainLayout td#navColumnRight { width: 16em; padding-left: %(defaultLayoutMargin)s; } @@ -313,28 +318,15 @@ color: %(defaultColor)s; } -/* rql bar */ - -div#rqlinput { - margin-bottom: %(defaultLayoutMargin)s; -} - -input#rql{ - padding: 0.25em 0.3em; - width: 99%; -} - -/* boxes */ +/* XXX old boxes, deprecated */ div.boxFrame { width: 100%; } div.boxTitle { - overflow: hidden; - font-weight: bold; color: #fff; - background: %(boxTitleBg)s; + background: %(contextualBoxTitleBgColor)s; } div.boxTitle span, @@ -343,14 +335,7 @@ white-space: nowrap; } -div.searchBoxFrame div.boxTitle, -div.greyBoxFrame div.boxTitle { - background: %(actionBoxTitleBg)s; -} - -div.sideBoxTitle span, -div.searchBoxFrame div.boxTitle span, -div.greyBoxFrame div.boxTitle span { +div.sideBoxTitle span { color: %(defaultColor)s; } @@ -364,34 +349,13 @@ border-top: none; } -a.boxMenu { - display: block; - padding: 1px 9px 1px 3px; - background: transparent %(bulletDownImg)s; -} - -a.boxMenu:hover { - background: %(sideBoxBodyBgColor)s %(bulletDownImg)s; - cursor: pointer; -} - -a.popupMenu { - background: transparent url("puce_down_black.png") 2% 6px no-repeat; - padding-left: 2em; -} - -div.searchBoxFrame div.boxContent { - padding: 4px 4px 3px; - background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; -} - div.shadow{ height: 14px; background: url("shadow.gif") no-repeat top right; } div.sideBoxTitle { - background: %(actionBoxTitleBg)s; + background: %(incontextBoxBodyBg)s; display: block; font-weight: bold; } @@ -412,11 +376,11 @@ div.sideBoxBody { padding: 0.2em 5px; - background: %(sideBoxBodyBg)s; + background: %(incontextBoxBodyBg)s; } div.sideBoxBody a { - color: %(sideBoxBodyColor)s; + color: %(incontextBoxBodyColor)s; } div.sideBoxBody a:hover { @@ -427,6 +391,174 @@ padding-right: 1em; } +/* boxes */ + +div.boxTitle { + overflow: hidden; + font-weight: bold; +} + +div.boxTitle span { + padding: 0px 0.5em; + white-space: nowrap; +} + +div.boxBody { + padding: 5px; + border-top: none; + background-color: %(leftrightBoxBodyBgColor)s; +} + +div.boxBody a { + color: %(leftrightBoxBodyColor)s; +} + +div.boxBody a:hover { + text-decoration: none; + cursor: pointer; + background-color: %(leftrightBoxBodyHoverBgColor)s; +} + +/* boxes contextual customization */ + +.contextFreeBox div.boxTitle { + background: %(contextFreeBoxTitleBg)s; + color: %(contextFreeBoxTitleColor)s; +} + +.contextualBox div.boxTitle { + background: %(contextualBoxTitleBg)s; + color: %(contextualBoxTitleColor)s; +} + +.primaryRight div.boxTitle { + background: %(incontextBoxTitleBg)s; + color: %(incontextBoxTitleColor)s; +} + +.primaryRight div.boxBody { + padding: 0.2em 5px; + background: %(incontextBoxBodyBgColor)s; +} + +.primaryRight div.boxBody a { + color: %(incontextBoxBodyColor)s; +} + +.primaryRight div.boxBody a:hover { + background-color: %(incontextBoxBodyHoverBgColor)s; +} + +.primaryRight div.boxFooter { + margin-bottom: 1em; +} + +#navColumnLeft div.boxFooter, #navColumnRight div.boxFooter{ + height: 14px; + background: url("shadow.gif") no-repeat top right; +} + +/* boxes lists and menus */ + +ul.boxListing { + margin: 0; + padding: 0; +} + +ul.boxListing ul { + padding: 1px 3px; +} + +ul.boxListing a { + color: %(defaultColor)s; + display: block; + padding: 1px 9px 1px 3px; +} + +ul.boxListing li { + margin: 0px; + padding: 0px; + background-image: none; +} + +ul.boxListing ul li { + margin: 0px; + padding-left: 8px; +} + +ul.boxListing ul li a { + padding-left: 10px; + background-image: url("bullet_orange.png"); + background-repeat: no-repeat; + background-position: 0 6px; +} + +ul.boxListing .selected { + color: %(aColor)s; + font-weight: bold; +} + +ul.boxListing a.boxMenu:hover { + border-top: medium none; + background: %(leftrightBoxBodyHoverBgColor)s; +} + +a.boxMenu, +ul.boxListing a.boxMenu{ + display: block; + padding: 1px 3px; + background: transparent %(bulletDownImg)s; +} + +ul.boxListing a.boxMenu:hover { + border-top: medium none; + background: %(leftrightBoxBodyHoverBgColor)s %(bulletDownImg)s; +} + +a.boxMenu:hover { + cursor: pointer; +} + +a.popupMenu { + background: transparent url("puce_down_black.png") 2% 6px no-repeat; + padding-left: 2em; +} + +/* custom boxes */ + +.search_box div.boxBody { + padding: 4px 4px 3px; + background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; +} + +.bookmarks_box ul.boxListing div a{ + background: #fff; + display: inline; + padding: 0; +} +.bookmarks_box ul.boxListing div a:hover{ + border-bottom: 1px solid #000; +} + +.download_box div.boxTitle { + background : #8fbc8f !important; +} + +.download_box div.boxBody { + background : #eefed9; +} + +/* search box and rql bar */ + +div#rqlinput { + margin-bottom: %(defaultLayoutMargin)s; +} + +input#rql{ + padding: 0.25em 0.3em; + width: 99%; +} + input.rqlsubmit{ display: block; width: 20px; @@ -436,7 +568,7 @@ } input#norql{ - width:13em; + width:155px; margin-right: 2px; } @@ -447,7 +579,7 @@ } div#userActionsBox { - width: 14em; + width: 15em; text-align: right; } @@ -457,20 +589,6 @@ padding-right: 2em; } -/* download box XXX move to its own file? */ -div.downloadBoxTitle{ - background : #8fbc8f; - font-weight: bold; -} - -div.downloadBox{ - font-weight: bold; -} - -div.downloadBox div.sideBoxBody{ - background : #eefed9; -} - /**************/ /* navigation */ /**************/ @@ -578,7 +696,7 @@ div#appMsg { margin-bottom: %(defaultLayoutMargin)s; - border: 1px solid %(actionBoxTitleBgColor)s; + border: 1px solid %(incontextBoxTitleBgColor)s; } .message { @@ -591,7 +709,7 @@ padding-left: 25px; background: %(msgBgColor)s url("critical.png") 2px center no-repeat; color: %(errorMsgColor)s; - border: 1px solid %(actionBoxTitleBgColor)s; + border: 1px solid %(incontextBoxTitleBgColor)s; } /* search-associate message */ @@ -701,6 +819,14 @@ margin-bottom: 0.2em; /* because vertical-align doesn't seems to have any effect */ } + +table.ajaxEditRelationTable{ + margin-bottom: 0.5em; +} +table.ajaxEditRelationTable td.entity{ + padding-left: 0.5em; +} + /***************************************/ /* error view (views/management.py) */ /***************************************/ @@ -746,7 +872,7 @@ input.button{ margin: 1em 1em 0px 0px; border: 1px solid %(buttonBorderColor)s; - border-color: %(buttonBorderColor)s %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s %(buttonBorderColor)s; + border-color: %(buttonBorderColor)s %(incontextBoxTitleBgColor)s %(incontextBoxTitleBgColor)s %(buttonBorderColor)s; } /* FileItemInnerView jquery.treeview.css */ @@ -766,74 +892,20 @@ ul.startup li, ul.section li { - margin-left:0px -} - -ul.boxListing { - margin: 0px; - padding: 0px 3px; -} - -ul.boxListing li, -ul.boxListing ul li { - margin: 0px; - padding: 0px; - background-image: none; -} - -ul.boxListing ul { - padding: 1px 3px; -} - -ul.boxListing a { - color: %(defaultColor)s; - display:block; - padding: 1px 9px 1px 3px; -} - -ul.boxListing .selected { - color: %(aColor)s; - font-weight: bold; -} - -ul.boxListing a.boxMenu:hover { - border-top: medium none; - background: %(sideBoxBodyBgColor)s %(bulletDownImg)s; -} - -ul.boxListing a.boxBookmark { - padding-left: 3px; - background-image: none; - background:#fff; + margin-left: 0px } ul.simple li, -ul.boxListing ul li , .popupWrapper ul li { background: transparent url("bullet_orange.png") no-repeat 0% 6px; } -ul.boxListing a.boxBookmark:hover, -ul.boxListing a:hover, -ul.boxListing ul li a:hover { - text-decoration: none; - background: %(sideBoxBodyBg)s; -} - -ul.boxListing ul li a:hover{ - background-color: transparent; -} - -ul.boxListing ul li a { - padding: 1px 3px 0px 10px; -} - ul.simple li { padding-left: 8px; } .popupWrapper ul { - padding:0.2em 0.3em; + padding: 0.2em 0.3em; margin-bottom: 0px; } @@ -866,7 +938,7 @@ .validateButton { margin: 1em 1em 0px 0px; border: 1px solid %(buttonBorderColor)s; - border-color: %(buttonBorderColor)s %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s %(buttonBorderColor)s; + border-color: %(buttonBorderColor)s %(incontextBoxTitleBgColor)s %(incontextBoxTitleBgColor)s %(buttonBorderColor)s; background: %(buttonBgColor)s url("button.png") bottom left repeat-x; } @@ -906,6 +978,11 @@ /* overwite other css here */ /********************************/ +.ui-menu li.ui-menu-item { + /* remove background image (orange bullet) for autocomplete suggestions */ + background-image: none; +} + /* ui.tabs.css */ ul.ui-tabs-nav, div.ui-tabs-panel { diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.edition.js Mon Oct 18 11:47:06 2010 +0200 @@ -591,176 +591,8 @@ log('got exception', ex); return false; } - function _callback(result, req) { + d.addCallback(function(result, req) { handleFormValidationResponse(formid, onsuccess, onfailure, result); - } - d.addCallback(_callback); + }); return false; } - - - -// ======================= DEPRECATED FUNCTIONS ========================= // -// (mostly reledit related) -/** - * .. function:: inlineValidateRelationFormOptions(rtype, eid, divid, options) - * - * called by reledit forms to submit changes - * * `rtype`, the attribute being edited - * - * * `eid`, the eid of the entity being edited - * - * * `options`, a dictionnary of options used by the form validation handler such - * as ``role``, ``onsuccess``, ``onfailure``, ``reload``, ``vid``, ``lzone`` - * and ``default_value``: - * - * * `onsucess`, javascript function to execute on success, default is noop - * - * * `onfailure`, javascript function to execute on failure, default is noop - * - * * `default_value`, value if the field is empty - * - * * `lzone`, html fragment (string) for a clic-zone triggering actual edition - */ - - -showInlineEditionForm = cw.utils.deprecatedFunction( - '[3.9] this is now unused by reledit (see cw.reledit.js)', - function showInlineEditionForm(eid, rtype, divid) { - jQuery('#' + divid).hide(); - jQuery('#' + divid + '-value').hide(); - jQuery('#' + divid + '-form').show(); - } -); - -hideInlineEdit = cw.utils.deprecatedFunction( - '[3.9] this is now unused by reledit (see cw.reledit.js)', - function hideInlineEdit(eid, rtype, divid) { - jQuery('#appMsg').hide(); - jQuery('div.errorMessage').remove(); - jQuery('#' + divid).show(); - jQuery('#' + divid + '-value').show(); - jQuery('#' + divid + '-form').hide(); - } -); - - -inlineValidateRelationFormOptions = cw.utils.deprecatedFunction( - '[3.9] this is now unused by reledit (see cw.reledit.js)', - function inlineValidateRelationFormOptions(rtype, eid, divid, options) { - try { - var form = cw.getNode(divid + '-form'); - var relname = rtype + ':' + eid; - var newtarget = jQuery('[name=' + relname + ']').val(); - var zipped = cw.utils.formContents(form); - var args = ajaxFuncArgs('validate_form', null, 'apply', zipped[0], zipped[1]); - var d = loadRemote(JSON_BASE_URL, args, 'POST'); - } catch(ex) { - return false; - } - d.addCallback(function(result, req) { - execFormValidationResponse(rtype, eid, divid, options, result); - }); - return false; - }); - -execFormValidationResponse = cw.utils.deprecatedFunction( - '[3.9] this is now unused by reledit (see cw.reledit.js)', - function execFormValidationResponse(rtype, eid, divid, options, result) { - options = $.extend({onsuccess: noop, - onfailure: noop - }, options); - if (handleFormValidationResponse(divid + '-form', options.onsucess , options.onfailure, result)) { - if (options.reload) { - document.location.reload(); - } else { - var args = { - fname: 'reledit_form', - rtype: rtype, - role: options.role, - eid: eid, - divid: divid, - reload: options.reload, - vid: options.vid, - default_value: options.default_value, - landing_zone: options.lzone - }; - jQuery('#' + divid + '-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post'); - } - } -}); - - -/** - * .. function:: loadInlineEditionFormOptions(eid, rtype, divid, options) - * - * inline edition - */ -loadInlineEditionFormOptions = cw.utils.deprecatedFunction( - '[3.9] this is now unused by reledit (see cw.reledit.js) ', - function loadInlineEditionFormOptions(eid, rtype, divid, options) { - var args = { - fname: 'reledit_form', - rtype: rtype, - role: options.role, - eid: eid, - divid: divid, - reload: options.reload, - vid: options.vid, - default_value: options.default_value, - landing_zone: options.lzone, - callback: function() { - showInlineEditionForm(eid, rtype, divid); - } - }; - jQuery('#' + divid + '-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post'); -}); - - -inlineValidateRelationForm = cw.utils.deprecatedFunction( - '[3.9] inlineValidateRelationForm() function is deprecated, use inlineValidateRelationFormOptions instead', - function(rtype, role, eid, divid, reload, vid, default_value, lzone, onsucess, onfailure) { - try { - var form = cw.getNode(divid + '-form'); - var relname = rtype + ':' + eid; - var newtarget = jQuery('[name=' + relname + ']').val(); - var zipped = cw.utils.formContents(form); - var d = asyncRemoteExec('validate_form', 'apply', zipped[0], zipped[1]); - } catch(ex) { - return false; - } - d.addCallback(function(result, req) { - var options = {role : role, - reload: reload, - vid: vid, - default_value: default_value, - lzone: lzone, - onsucess: onsucess || $.noop, - onfailure: onfailure || $.noop - }; - execFormValidationResponse(rtype, eid, divid, options); - }); - return false; - } -); - -loadInlineEditionForm = cw.utils.deprecatedFunction( - '[3.9] loadInlineEditionForm() function is deprecated, use loadInlineEditionFormOptions instead', - function(eid, rtype, role, divid, reload, vid, default_value, lzone) { - var args = { - fname: 'reledit_form', - rtype: rtype, - role: role, - eid: eid, - divid: divid, - reload: reload, - vid: vid, - default_value: default_value, - landing_zone: lzone, - callback: function() { - showInlineEditionForm(eid, rtype, divid); - } - }; - jQuery('#' + divid + '-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post'); - } -); diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.form.css --- a/web/data/cubicweb.form.css Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.form.css Mon Oct 18 11:47:06 2010 +0200 @@ -229,6 +229,6 @@ margin: 1em 1em 0px 0px; border-width: 1px; border-style: solid; - border-color: %(buttonBorderColor)s %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s %(buttonBorderColor)s; + border-color: %(buttonBorderColor)s %(incontextBoxBodyBgColor)s %(incontextBoxBodyBgColor)s %(buttonBorderColor)s; background: %(buttonBgColor)s %(buttonBgImg)s; } diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.htmlhelpers.js --- a/web/data/cubicweb.htmlhelpers.js Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.htmlhelpers.js Mon Oct 18 11:47:06 2010 +0200 @@ -1,3 +1,14 @@ +/* in CW 3.10, we should move these functions in this namespace */ +cw.htmlhelpers = new Namespace('cw.htmlhelpers'); + +jQuery.extend(cw.htmlhelpers, { + popupLoginBox: function(loginboxid, focusid) { + $('#'+loginboxid).toggleClass('hidden'); + jQuery('#' + focusid +':visible').focus(); + } +}); + + /** * .. function:: baseuri() * @@ -97,10 +108,11 @@ * toggles visibility of login popup div */ // XXX used exactly ONCE in basecomponents -function popupLoginBox() { - $('#popupLoginBox').toggleClass('hidden'); - jQuery('#__login:visible').focus(); -} +popupLoginBox = cw.utils.deprecatedFunction( + function() { + $('#popupLoginBox').toggleClass('hidden'); + jQuery('#__login:visible').focus(); +}); /** * .. function getElementsMatching(tagName, properties, \/* optional \*\/ parent) diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.js --- a/web/data/cubicweb.js Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.js Mon Oct 18 11:47:06 2010 +0200 @@ -6,6 +6,15 @@ cw = new Namespace('cw'); jQuery.extend(cw, { + cubes: new Namespace('cubes'), + /* provide a removeEventListener / detachEvent definition to + * to bypass a jQuery 1.4.2 bug when unbind() is called on a + * plain JS object and not a DOM node. + * see http://dev.jquery.com/ticket/6184 for more details + */ + removeEventListener: function() {}, + detachEvent: function() {}, + log: function () { var args = []; for (var i = 0; i < arguments.length; i++) { @@ -299,6 +308,17 @@ }, /** + * .. function:: difference(lst1, lst2) + * + * returns a list containing all elements in `lst1` that are not + * in `lst2`. + */ + difference: function(lst1, lst2) { + return jQuery.grep(lst1, function(elt, i) { + return jQuery.inArray(elt, lst2) == -1; + }); + }, + /** * .. function:: domid(string) * * return a valid DOM id from a string (should also be usable in jQuery @@ -415,22 +435,17 @@ } // XXX avoid crashes / backward compat -CubicWeb = { +CubicWeb = cw; + +jQuery.extend(cw, { require: cw.utils.deprecatedFunction( '[3.9] CubicWeb.require() is not used anymore', function(module) {}), provide: cw.utils.deprecatedFunction( '[3.9] CubicWeb.provide() is not used anymore', function(module) {}) -}; +}); jQuery(document).ready(function() { - jQuery(CubicWeb).trigger('server-response', [false, document]); - jQuery(cw).trigger('server-response', [false, document]); + $(cw).trigger('server-response', [false, document]); }); - -// XXX as of 2010-04-07, no known cube uses this -jQuery(CubicWeb).bind('ajax-loaded', function() { - log('[3.7] "ajax-loaded" event is deprecated, use "server-response" instead'); - jQuery(cw).trigger('server-response', [false, document]); -}); diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.login.css --- a/web/data/cubicweb.login.css Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.login.css Mon Oct 18 11:47:06 2010 +0200 @@ -5,20 +5,20 @@ * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr */ -div#popupLoginBox { +div.popupLoginBox { position: absolute; z-index: 400; right: 0px; width: 26em; padding: 0px 1px 1px; - background: %(listingBorderColor)s; + background: %(listingBorderColor)s; } -div#popupLoginBox label{ +div.popupLoginBox label{ font-weight: bold; } -div#popupLoginBox div#loginContent { +div.popupLoginBox div.loginContent { background: #e6e4ce; padding: 5px 3px 4px; } @@ -30,7 +30,7 @@ margin-left: -14em; width: 28em; background: #fff; - border: 2px solid %(actionBoxTitleBgColor)s; + border: 2px solid %(incontextBoxBodyBgColor)s; padding-bottom: 0.5em; text-align: center; } @@ -40,7 +40,7 @@ font-size: 140%; } -div#loginTitle { +div.loginTitle { color: #fff; font-weight: bold; font-size: 140%; @@ -49,18 +49,19 @@ background: %(headerBgColor)s url("banner.png") repeat-x top left; } -div#loginBox div#loginContent form { +div#loginBox div.loginContent form { padding-top: 1em; width: 90%; margin: auto; } -#popupLoginBox table td { + +.popupLoginBox table td { padding: 0px 3px; white-space: nowrap; } -#loginContent table { +.loginContent table { padding: 0px 0.5em; margin: auto; } @@ -74,17 +75,20 @@ margin-top: 0.6em; } -#loginContent input.data { +.loginContent input.data { width: 12em; } +/* This is seriously debatable + These buttons render much more clearly as standard/default buttons + */ .loginButton { border: 1px solid #edecd2; - border-color: #edecd2 %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s #edecd2; + border-color: #edecd2 %(incontextBoxBodyBgColor)s %(incontextBoxBodyBgColor)s #edecd2; margin: 2px 0px 0px; background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; } -#loginContent .formButtonBar { +.loginContent .formButtonBar { float: right; } diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.old.css --- a/web/data/cubicweb.old.css Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.old.css Mon Oct 18 11:47:06 2010 +0200 @@ -22,7 +22,8 @@ font-family: Verdana, sans-serif; } -h1 { +h1, +.vtitle { font-size: 188%; margin: 0.2em 0px 0.3em; border-bottom: 1px solid #000; @@ -228,6 +229,7 @@ table#header { background: #ff7700 url("banner.png") left top repeat-x; text-align: left; + width: 100%; } table#header td { @@ -235,17 +237,22 @@ } table#header a { -color: #000; + color: #000; +} + +table#header td#headtext { + float: left; +} + +table#header td#header-right { + padding-top: 1em; + float: right; } span#appliName { - font-weight: bold; - color: #000; - white-space: nowrap; -} - -table#header td#headtext { - width: 100%; + font-weight: bold; + color: #000; + white-space: nowrap; } /* FIXME appear with 4px width in IE6 */ @@ -254,10 +261,6 @@ } /* Popup on login box and userActionBox */ -div.popupWrapper{ - position:relative; - z-index:100; -} div.popup { position: absolute; @@ -314,18 +317,23 @@ div#rqlinput { border: 1px solid #cfceb7; margin-bottom: 8px; - padding: 3px; + padding: 1px; background: #cfceb7; + width: 100%; +} + +input#rql { + width: 99%; } -input#rql{ - width: 95%; +input.rqlsubmit{ + display: block; + width: 20px; + height: 20px; + background: %(buttonBgColor)s url("go.png") 50% 50% no-repeat; + vertical-align: bottom; } - -/* boxes */ -div.navboxes { - margin-top: 8px; -} +/* old boxes, deprecated */ div.boxFrame { width: 100%; @@ -335,25 +343,17 @@ padding-top: 0px; padding-bottom: 0.2em; font: bold 100% Georgia; - overflow: hidden; color: #fff; background: #ff9900 url("search.png") left bottom repeat-x; } -div.searchBoxFrame div.boxTitle, -div.greyBoxFrame div.boxTitle { - background: #cfceb7; -} - div.boxTitle span, div.sideBoxTitle span { padding: 0px 5px; white-space: nowrap; } -div.sideBoxTitle span, -div.searchBoxFrame div.boxTitle span, -div.greyBoxFrame div.boxTitle span { +div.sideBoxTitle span { color: #222211; } @@ -367,85 +367,6 @@ border-top: none; } -ul.boxListing { - margin: 0px; - padding: 0px 3px; -} - -ul.boxListing li, -ul.boxListing ul li { - display: inline; - margin: 0px; - padding: 0px; - background-image: none; -} - -ul.boxListing ul { - margin: 0px 0px 0px 7px; - padding: 1px 3px; -} - -ul.boxListing a { - color: #000; - display: block; - padding: 1px 9px 1px 3px; -} - -ul.boxListing .selected { - color: #FF4500; - font-weight: bold; -} - -ul.boxListing a.boxBookmark:hover, -ul.boxListing a:hover, -ul.boxListing ul li a:hover { - text-decoration: none; - background: #eeedd9; - color: #111100; -} - -ul.boxListing a.boxMenu:hover { - background: #eeedd9 url(puce_down.png) no-repeat scroll 98% 6px; - cursor:pointer; - border-top:medium none; - } -a.boxMenu { - background: transparent url("puce_down.png") 98% 6px no-repeat; - display: block; - padding: 1px 9px 1px 3px; -} - - -a.popupMenu { - background: transparent url("puce_down_black.png") 2% 6px no-repeat; - padding-left: 2em; -} - -ul.boxListing ul li a:hover { - background: #eeedd9 url("bullet_orange.png") 0% 6px no-repeat; -} - -a.boxMenu:hover { - background: #eeedd9 url("puce_down.png") 98% 6px no-repeat; - cursor: pointer; -} - -ul.boxListing a.boxBookmark { - padding-left: 3px; - background-image:none; - background:#fff; -} - -ul.boxListing ul li a { - background: #fff url("bullet_orange.png") 0% 6px no-repeat; - padding: 1px 3px 0px 10px; -} - -div.searchBoxFrame div.boxContent { - padding: 4px 4px 3px; - background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; -} - div.shadow{ height: 14px; background: url("shadow.gif") no-repeat top right; @@ -485,16 +406,168 @@ padding-right: 1em; } -input.rqlsubmit{ - background: #fffff8 url("go.png") 50% 50% no-repeat; - width: 20px; - height: 20px; - margin: 0px; +/* boxes */ + +div.navboxes { + padding-top: 0.5em; +} + +div.boxTitle { + overflow: hidden; + font-weight: bold; +} + +div.boxTitle span { + padding: 0px 0.5em; + white-space: nowrap; +} + +div.boxBody { + padding: 3px 3px; + border-top: none; + background-color: %(leftrightBoxBodyBgColor)s; +} + +div.boxBody a { + color: %(leftrightBoxBodyColor)s; +} + +div.boxBody a:hover { + text-decoration: none; + cursor: pointer; + background-color: %(leftrightBoxBodyHoverBgColor)s; +} + +/* boxes contextual customization */ + +.contextFreeBox div.boxTitle { + background: %(contextFreeBoxTitleBg)s; + color: %(contextFreeBoxTitleColor)s; +} + +.contextualBox div.boxTitle { + background: %(contextualBoxTitleBg)s; + color: %(contextualBoxTitleColor)s; +} + +.primaryRight div.boxTitle { + background: %(incontextBoxTitleBg)s; + color: %(incontextBoxTitleColor)s; +} + +.primaryRight div.boxBody { + padding: 0.2em 5px; + background: %(incontextBoxBodyBgColor)s; +} + +.primaryRight div.boxBody a { + color: %(incontextBoxBodyColor)s; +} + +.primaryRight div.boxBody a:hover { + background-color: %(incontextBoxBodyHoverBgColor)s; +} + +.primaryRight div.boxFooter { + margin-bottom: 1em; +} + +#navColumnLeft div.boxFooter, #navColumnRight div.boxFooter{ + height: 14px; + background: url("shadow.gif") no-repeat top right; +} + +/* boxes lists and menus */ + +ul.boxListing { + margin: 0; + padding: 0; } -input#norql{ - width:13em; - margin-right: 2px; +ul.boxListing ul { + padding: 1px 3px; +} + +ul.boxListing a { + color: %(defaultColor)s; + display: block; + padding: 1px 3px; +} + +ul.boxListing li { + margin: 0px; + padding: 0px; + background-image: none; +} + +ul.boxListing ul li { + margin: 0px; + padding-left: 1em; +} + +ul.boxListing ul li a { + padding-left: 10px; + background-image: url("bullet_orange.png"); + background-repeat: no-repeat; + background-position: 0 6px; +} + +ul.boxListing .selected { + color: %(aColor)s; + font-weight: bold; +} + +ul.boxListing a.boxMenu:hover { + border-top: medium none; + background: %(leftrightBoxBodyHoverBgColor)s; +} + +a.boxMenu, +ul.boxListing a.boxMenu{ + display: block; + padding: 1px 3px; + background: transparent %(bulletDownImg)s; +} + +ul.boxListing a.boxMenu:hover { + border-top: medium none; + background: %(leftrightBoxBodyHoverBgColor)s %(bulletDownImg)s; +} + +a.boxMenu:hover { + cursor: pointer; +} + +a.popupMenu { + background: transparent url("puce_down_black.png") 2% 6px no-repeat; + padding-left: 2em; +} + + +/* custom boxes */ + +.search_box div.boxBody { + padding: 4px 4px 3px; + background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; +} + +.bookmarks_box ul.boxListing div a { + background: #fff; + display: inline; + padding: 0; +} + +.bookmarks_box ul.boxListing div { + padding-bottom: 0.3em; +} + +.download_box div.boxTitle { + background : #8fbc8f !important; +} + +.download_box div.boxBody { + background : #eefed9; + vertical-align: center; } /* user actions menu */ @@ -514,20 +587,6 @@ padding-right: 2em; } -/* download box XXX move to its own file? */ -div.downloadBoxTitle{ - background : #8FBC8F; - font-weight: bold; -} - -div.downloadBox{ - font-weight: bold; -} - -div.downloadBox div.sideBoxBody{ - background : #EEFED9; -} - /**************/ /* navigation */ /**************/ @@ -770,6 +829,13 @@ margin-bottom: 0.2em; /* because vertical-align doesn't seems to have any effect */ } +table.ajaxEditRelationTable{ + margin-bottom: 0.5em; +} +table.ajaxEditRelationTable td.entity{ + padding-left: 0.5em; +} + /***************************************/ /* error view (views/management.py) */ /***************************************/ @@ -884,3 +950,12 @@ .releditForm { display:none; } + +/********************************/ +/* overwite other css here */ +/********************************/ + +.ui-menu li.ui-menu-item { + /* remove background image (orange bullet) for autocomplete suggestions */ + background-image: none; +} diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.reledit.js --- a/web/data/cubicweb.reledit.js Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.reledit.js Mon Oct 18 11:47:06 2010 +0200 @@ -52,7 +52,7 @@ return; } } - jQuery('#'+params.divid+'-reledit').parent().loadxhtml(JSON_BASE_URL, params, 'post'); + jQuery('#'+params.divid+'-reledit').loadxhtml(JSON_BASE_URL, params, 'post'); jQuery(cw).trigger('reledit-reloaded', params); }, @@ -68,7 +68,7 @@ pageid: pageid, eid: eid, divid: divid, formid: formid, reload: reload, vid: vid}; - var d = jQuery('#'+divid+'-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post'); + var d = jQuery('#'+divid+'-reledit').loadxhtml(JSON_BASE_URL, args, 'post'); d.addCallback(function () {cw.reledit.showInlineEditionForm(divid);}); } }); diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.tableview.css --- a/web/data/cubicweb.tableview.css Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.tableview.css Mon Oct 18 11:47:06 2010 +0200 @@ -6,7 +6,7 @@ font-weight: bold; background: #ebe8d9 url("button.png") repeat-x; padding: 0.3em; - border-bottom: 1px solid %(actionBoxTitleBgColor)s; + border-bottom: 1px solid %(incontextBoxBodyBgColor)s; text-align: left; } diff -r 972bd504daf6 -r 308037210dab web/data/cubicweb.widgets.js --- a/web/data/cubicweb.widgets.js Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/cubicweb.widgets.js Mon Oct 18 11:47:06 2010 +0200 @@ -56,67 +56,247 @@ return jQuery.get(url, data, callback, 'json'); } + +(function ($) { + var defaultSettings = { + initialvalue: '', + multiple: false, + mustMatch: false, + delay: 50, + limit: 50 + }; + function split(val) { return val.split( /\s*,\s*/ ); } + function extractLast(term) { return split(term).pop(); } + function allButLast(val) { + var terms = split(val); + terms.pop(); + return terms; + } + + var methods = { + __init__: function(suggestions, options) { + return this.each(function() { + // here, `this` refers to the DOM element (e.g. input) being wrapped + // by cwautomplete plugin + var instanceData = $(this).data('cwautocomplete'); + if (instanceData) { + // already initialized + return; + } + var settings = $.extend({}, defaultSettings, options); + instanceData = { + initialvalue: settings.initialvalue, + userInput: this, + hiddenInput: null + }; + var hiHandlers = methods.hiddenInputHandlers; + $(this).data('cwautocomplete', instanceData); + $.ui.autocomplete.prototype._search = methods.search; + if (settings.multiple) { + $.ui.autocomplete.filter = methods.multiple.makeFilter(this); + $(this).bind({ + autocompleteselect: methods.multiple.select, + autocompletefocus: methods.multiple.focus, + keydown: methods.multiple.keydown + }); + } + // XXX katia we dont need it if minLength == 0, but by setting minLength = 0 + // we probably break the backward compatibility + $(this).bind('blur', methods.blur); + if ($.isArray(suggestions)) { // precomputed list of suggestions + settings.source = hiHandlers.checkSuggestionsDataFormat(instanceData, suggestions); + } else { // url to call each time something is typed + settings.source = function(request, response) { + var d = loadRemote(suggestions, {q: request.term, limit: settings.limit}, 'POST'); + d.addCallback(function (suggestions) { + suggestions = hiHandlers.checkSuggestionsDataFormat(instanceData, suggestions); + response(suggestions); + if((suggestions.length) == 0){ + methods.resetValues(instanceData); + } + }); + }; + } + $(this).autocomplete(settings); + if (settings.mustMatch) { + $(this).keypress(methods.ensureExactMatch); + } + }); + }, + + multiple: { + focus: function() { + // prevent value inserted on focus + return false; + }, + select: function(event, ui) { + var terms = allButLast(this.value); + // add the selected item + terms.push(ui.item.value); + // add placeholder to get the comma-and-space at the end + terms.push(""); + this.value = terms.join( ", " ); + return false; + }, + keydown: function(evt) { + if ($(this).data('autocomplete').menu.active && evt.keyCode == $.ui.keyCode.TAB) { + evt.preventDefault(); + } + }, + makeFilter: function(userInput) { + return function(array, term) { + // remove already entered terms from suggestion list + array = cw.utils.difference(array, allButLast(userInput.value)); + var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); + return $.grep( array, function(value) { + return matcher.test( value.label || value.value || value ); + }); + }; + } + }, + blur: function(evt){ + var instanceData = $(this).data('cwautocomplete'); + if($(instanceData.userInput).val().strip().length==0){ + methods.resetValues(instanceData); + } + }, + search: function(value) { + this.element.addClass("ui-autocomplete-loading"); + if (this.options.multiple) { + value = extractLast(value); + } + this.source({term: value}, this.response); + }, + ensureExactMatch: function(evt) { + var instanceData = $(this).data('cwautocomplete'); + if (evt.keyCode == $.ui.keyCode.ENTER || evt.keyCode == $.ui.keyCode.TAB) { + var validChoices = $.map($('ul.ui-autocomplete li'), + function(li) {return $(li).text();}); + if ($.inArray($(instanceData.userInput).val(), validChoices) == -1) { + $(instanceData.userInput).val(''); + $(instanceData.hiddenInput).val(instanceData.initialvalue || ''); + } + } + }, + resetValues: function(instanceData){ + $(instanceData.userInput).val(''); + $(instanceData.hiddenInput).val(''); + }, + + + hiddenInputHandlers: { + /** + * `hiddenInputHandlers` defines all methods specific to handle the + * hidden input created along the standard text input. + * An hiddenInput is necessary when displayed suggestions are + * different from actual values to submit. + * Imagine an autocompletion widget to choose among a list of CWusers. + * Suggestions would be the list of logins, but actual values would + * be the corresponding eids. + * To handle such cases, suggestions list should be a list of JS objects + * with two `label` and `value` properties. + **/ + suggestionSelected: function(evt, ui) { + var instanceData = $(this).data('cwautocomplete'); + instanceData.hiddenInput.value = ui.item.value; + instanceData.value = ui.item.label; + return false; // stop propagation + }, + + suggestionFocusChanged: function(evt, ui) { + var instanceData = $(this).data('cwautocomplete'); + instanceData.userInput.value = ui.item.label; + return false; // stop propagation + }, + + needsHiddenInput: function(suggestions) { + return suggestions[0].label !== undefined; + }, + initializeHiddenInput: function(instanceData) { + var userInput = instanceData.userInput; + var hiddenInput = INPUT({ + type: "hidden", + name: userInput.name, + // XXX katia : this must be handeled in .SuggestField widget, but + // it seems not to be used anymore + value: $(userInput).attr('cubicweb:initialvalue') || userInput.value + }); + $(userInput).removeAttr('name').after(hiddenInput); + instanceData.hiddenInput = hiddenInput; + $(userInput).bind({ + autocompleteselect: methods.hiddenInputHandlers.suggestionSelected, + autocompletefocus: methods.hiddenInputHandlers.suggestionFocusChanged + }); + }, + + /* + * internal convenience function: old jquery plugin accepted to be fed + * with a list of couples (value, label). The new (jquery-ui) autocomplete + * plugin expects a list of objects with "value" and "label" properties. + * + * This function converts the old format to the new one. + */ + checkSuggestionsDataFormat: function(instanceData, suggestions) { + // check for old (value, label) format + if ($.isArray(suggestions) && suggestions.length && + $.isArray(suggestions[0])){ + if (suggestions[0].length == 2) { + cw.log('[3.10] autocomplete init func should return {label,value} dicts instead of lists'); + suggestions = $.map(suggestions, function(sugg) { + return {value: sugg[0], label: sugg[1]}; + }); + } else { + if(suggestions[0].length == 1){ + suggestions = $.map(suggestions, function(sugg) { + return {value: sugg[0], label: sugg[0]}; + }); + } + } + } + var hiHandlers = methods.hiddenInputHandlers; + if (suggestions.length && hiHandlers.needsHiddenInput(suggestions) + && !instanceData.hiddenInput) { + hiHandlers.initializeHiddenInput(instanceData); + hiHandlers.fixUserInputInitialValue(instanceData, suggestions); + } + // otherwise, assume data shape is correct + return suggestions; + }, + + fixUserInputInitialValue: function(instanceData, suggestions) { + // called when the data is loaded to reset the correct displayed + // value in the visible input field (typically replacing an eid + // by a displayable value) + var curvalue = instanceData.userInput.value; + if (!curvalue) { + return; + } + for (var i=0, length=suggestions.length; i < length; i++) { + var sugg = suggestions[i]; + if (sugg.value == curvalue) { + instanceData.userInput.value = sugg.label; + return; + } + } + } + } + }; + + $.fn.cwautocomplete = function(data, options) { + return methods.__init__.apply(this, [data, options]); + }; +})(jQuery); + + Widgets.SuggestField = defclass('SuggestField', null, { __init__: function(node, options) { - var multi = node.getAttribute('cubicweb:multi') || "no"; options = options || {}; + var multi = node.getAttribute('cubicweb:multi'); options.multiple = (multi == "yes") ? true: false; - var dataurl = node.getAttribute('cubicweb:dataurl'); - var method = postJSON; - if (options.method == 'get') { - method = function(url, data, callback) { - // We can't rely on jQuery.getJSON because the server - // might set the Content-Type's response header to 'text/plain' - jQuery.get(url, data, function(response) { - callback(cw.evalJSON(response)); - }); - }; - } - var self = this; // closure - method(dataurl, null, function(data) { - // in case we received a list of couple, we assume that the first - // element is the real value to be sent, and the second one is the - // value to be displayed - if (data.length && data[0].length == 2) { - options.formatItem = function(row) { - return row[1]; - }; - self.hideRealValue(node); - self.setCurrentValue(node, data); - } - jQuery(node).autocomplete(data, options); + var d = loadRemote(node.getAttribute('cubicweb:dataurl')); + d.addCallback(function(data) { + $(node).cwautocomplete(data, options); }); - }, - - hideRealValue: function(node) { - var hidden = INPUT({ - 'type': "hidden", - 'name': node.name, - 'value': node.value - }); - node.parentNode.appendChild(hidden); - // remove 'name' attribute from visible input so that it is not submitted - // and set correct value in the corresponding hidden field - jQuery(node).removeAttr('name').bind('result', function(_, row, _) { - hidden.value = row[0]; - }); - }, - - setCurrentValue: function(node, data) { - // called when the data is loaded to reset the correct displayed - // value in the visible input field (typically replacing an eid - // by a displayable value) - var curvalue = node.value; - if (!node.value) { - return; - } - for (var i = 0, length = data.length; i < length; i++) { - var row = data[i]; - if (row[0] == curvalue) { - node.value = row[1]; - return; - } - } } }); @@ -124,84 +304,35 @@ __init__: function(node) { Widgets.SuggestField.__init__(this, node, { - method: 'get' + method: 'get' // XXX }); } }); Widgets.RestrictedSuggestField = defclass('RestrictedSuggestField', [Widgets.SuggestField], { - __init__: function(node) { Widgets.SuggestField.__init__(this, node, { mustMatch: true }); } +}); -}); //remote version of RestrictedSuggestField Widgets.LazySuggestField = defclass('LazySuggestField', [Widgets.SuggestField], { __init__: function(node, options) { var self = this; - var multi = "no"; options = options || {}; - options.max = 50; options.delay = 50; - options.cacheLength = 0; - options.mustMatch = true; // multiple selection not supported yet (still need to formalize correctly // initial values / display values) var initialvalue = cw.evalJSON(node.getAttribute('cubicweb:initialvalue') || 'null'); if (!initialvalue) { initialvalue = node.value; } - options = jQuery.extend({ - dataType: 'json', - multiple: (multi == "yes") ? true: false, - parse: this.parseResult - }, - options); - var dataurl = node.getAttribute('cubicweb:dataurl'); - // remove 'name' from original input and add the hidden one that will - // store the actual value - var hidden = INPUT({ - 'type': "hidden", - 'name': node.name, - 'value': initialvalue - }); - node.parentNode.appendChild(hidden); - jQuery(node).bind('result', { - hinput: hidden, - input: node - }, - self.hideRealValue).removeAttr('name').autocomplete(dataurl, options); - }, - - hideRealValue: function(evt, data, value) { - if (!value) { - value = ""; - } - evt.data.hinput.value = value; - }, - - /* - * @param data: a list of couple (value, label) to fill the suggestion list, - * (returned by CW through AJAX) - */ - parseResult: function(data) { - var parsed = []; - for (var i = 0; i < data.length; i++) { - var value = '' + data[i][0]; // a string is required later by jquery.autocomplete.js - var label = data[i][1]; - parsed[parsed.length] = { - data: [label], - value: value, - result: label - }; - }; - return parsed; + options.initialvalue = initialvalue; + Widgets.SuggestField.__init__(this, node, options); } - }); /** diff -r 972bd504daf6 -r 308037210dab web/data/images/ui-icons_222222_256x240.png Binary file web/data/images/ui-icons_222222_256x240.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/images/ui-icons_228ef1_256x240.png Binary file web/data/images/ui-icons_228ef1_256x240.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/images/ui-icons_ef8c08_256x240.png Binary file web/data/images/ui-icons_ef8c08_256x240.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/images/ui-icons_ffd27a_256x240.png Binary file web/data/images/ui-icons_ffd27a_256x240.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/images/ui-icons_ffffff_256x240.png Binary file web/data/images/ui-icons_ffffff_256x240.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/incontextBoxHeader.png Binary file web/data/incontextBoxHeader.png has changed diff -r 972bd504daf6 -r 308037210dab web/data/jquery.autocomplete.css --- a/web/data/jquery.autocomplete.css Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -.ac_results { - padding: 0px; - border: 1px solid black; - background-color: white; - overflow: hidden; - z-index: 99999; -} - -.ac_results ul { - width: 100%; - list-style-position: outside; - list-style: none; - padding: 0; - margin: 0; -} - -.ac_results li { - margin: 0px; - padding: 2px 5px; - cursor: default; - display: block; - /* - if width will be 100% horizontal scrollbar will apear - when scroll mode will be used - */ - /*width: 100%;*/ - font: menu; - font-size: 12px; - /* - it is very important, if line-height not setted or setted - in relative units scroll will be broken in firefox - */ - line-height: 16px; - overflow: hidden; - background-image: none; - padding: 0px 0px 1px 1px; -} - -.ac_loading { - background: white url('indicator.gif') right center no-repeat; -} - -.ac_odd { - background-color: #eee; -} - -.ac_over { - background-color: #0A246A; - color: white; -} diff -r 972bd504daf6 -r 308037210dab web/data/jquery.autocomplete.js --- a/web/data/jquery.autocomplete.js Mon Oct 18 11:04:19 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -/* - * jQuery Autocomplete plugin 1.1 - * - * Copyright (c) 2010 Jörn Zaefferer - * - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * Revision: $Id: jquery.autocomplete.js 15 2010-08-22 10:30:27Z joern.zaefferer $ - */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){hasFocus=1;lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i1){var seperator=options.multipleSeparator.length;var cursorAt=$(input).selection().start;var wordAt,progress=0;$.each(words,function(i,word){progress+=word.length;if(cursorAt<=progress){wordAt=i;return false;}progress+=seperator;});words[wordAt]=v;v=words.join(options.multipleSeparator);}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&¤tValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value)return[""];if(!options.multiple)return[$.trim(value)];return $.map(value.split(options.multipleSeparator),function(word){return $.trim(value).length?$.trim(word):null;});}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);if(words.length==1)return words[0];var cursorAt=$(input).selection().start;if(cursorAt==value.length){words=trimWords(value)}else{words=trimWords(value.replace(value.substring(cursorAt),""));}return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$(input).selection(previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else{$input.val("");$input.trigger("result",null);}}});}};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"$1");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(options.matchContains=="word"){i=s.toLowerCase().search("\\b"+sub.toLowerCase());}if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else -if(data[q]){return data[q];}else -if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("
    ").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("
      ").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName.toUpperCase()!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.fn.selection=function(start,end){if(start!==undefined){return this.each(function(){if(this.createTextRange){var selRange=this.createTextRange();if(end===undefined||start==end){selRange.move("character",start);selRange.select();}else{selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}}else if(this.setSelectionRange){this.setSelectionRange(start,end);}else if(this.selectionStart){this.selectionStart=start;this.selectionEnd=end;}});}var field=this[0];if(field.createTextRange){var range=document.selection.createRange(),orig=field.value,teststring="<->",textLength=range.text.length;range.text=teststring;var caretAt=field.value.indexOf(teststring);field.value=orig;this.selection(caretAt,caretAt+textLength);return{start:caretAt,end:caretAt+textLength}}else if(field.selectionStart!==undefined){return{start:field.selectionStart,end:field.selectionEnd}}};})(jQuery); \ No newline at end of file diff -r 972bd504daf6 -r 308037210dab web/data/jquery.js --- a/web/data/jquery.js Mon Oct 18 11:04:19 2010 +0200 +++ b/web/data/jquery.js Mon Oct 18 11:47:06 2010 +0200 @@ -1,19 +1,6240 @@ -/* - * jQuery JavaScript Library v1.3.2 +/*! + * jQuery JavaScript Library v1.4.2 * http://jquery.com/ * - * Copyright (c) 2010 John Resig - * Dual licensed under the MIT and GPL licenses. - * http://docs.jquery.com/License + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. * - * Date: 2010-02-19 17:34:21 -0500 (Thu, 19 Feb 2010) - * Revision: 6246 + * Date: Sat Feb 13 22:33:48 2010 -0500 */ -(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
      "]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
      ","
      "]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +(function( window, undefined ) { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwnProperty = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + indexOf = Array.prototype.indexOf; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + if ( elem ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && /^\w+$/.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + return jQuery.merge( this, selector ); + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return jQuery( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.4.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging object literal values or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) { + var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src + : jQuery.isArray(copy) ? [] : {}; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 13 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, i = 0; + while ( (fn = readyList[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Reset the list of functions + readyList = null; + } + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + return jQuery.ready(); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor + && !hasOwnProperty.call(obj, "constructor") + && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwnProperty.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") + .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + trim: function( text ) { + return (text || "").replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + if ( !inv !== !callback( elems[ i ], i ) ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch( error ) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + +// Mutifunctional method to get and set values to a collection +// The value/s can be optionally by executed if its a function +function access( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; +} + +function now() { + return (new Date).getTime(); +} +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + now(); + + div.style.display = "none"; + div.innerHTML = "
      a"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected, + + parentNode: div.removeChild( div.appendChild( document.createElement("div") ) ).parentNode === null, + + // Will be defined later + deleteExpando: true, + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null + }; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = ""; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + document.body.removeChild( div ).style.display = 'none'; + + div = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; +var expando = "jQuery" + now(), uuid = 0, windowData = {}; + +jQuery.extend({ + cache: {}, + + expando:expando, + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + "object": true, + "applet": true + }, + + data: function( elem, name, data ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache; + + if ( !id && typeof name === "string" && data === undefined ) { + return null; + } + + // Compute a unique ID for the element + if ( !id ) { + id = ++uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + elem[ expando ] = id; + thisCache = cache[ id ] = jQuery.extend(true, {}, name); + + } else if ( !cache[ id ] ) { + elem[ expando ] = id; + cache[ id ] = {}; + } + + thisCache = cache[ id ]; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + // Completely remove the data cache + delete cache[ id ]; + } + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + if ( typeof key === "undefined" && this.length ) { + return jQuery.data( this[0] ); + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + } + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } else { + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() { + jQuery.data( this, key, value ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i, elem ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); +var rclass = /[\n\t]/g, + rspace = /\s+/, + rreturn = /\r/g, + rspecialurl = /href|src|style/, + rtype = /(button|input)/i, + rfocusable = /(button|input|object|select|textarea)/i, + rclickable = /^(a|area)$/i, + rradiocheck = /radio|checkbox/; + +jQuery.fn.extend({ + attr: function( name, value ) { + return access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspace ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", setClass = elem.className; + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split(rspace); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, i = 0, self = jQuery(this), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Typecast each time if the value is a Function and the appended + // value is therefore different each time. + if ( typeof val === "number" ) { + val += ""; + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + if ( name in elem && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + elem[ name ] = value; + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + + // elem is actually elem.style ... set the style + // Using attr for specific style information is now deprecated. Use style instead. + return jQuery.style( elem, name, value ); + } +}); +var rnamespaces = /\.(.*)$/, + fcleanup = function( nm ) { + return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { + return "\\" + ch; + }); + }; + /* - * Sizzle CSS Selector Engine - v0.9.3 - * Copyright 2010, The Dojo Foundation + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events = elemData.events || {}, + eventHandle = elemData.handle, eventHandle; + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + handleObj.guid = handler.guid; + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)") + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( var j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( var j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[expando] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery.data( elem, "handle" ); + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var target = event.target, old, + isClick = jQuery.nodeName(target, "a") && type === "click", + special = jQuery.event.special[ type ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ type ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + type ]; + + if ( old ) { + target[ "on" + type ] = null; + } + + jQuery.event.triggered = true; + target[ type ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( old ) { + target[ "on" + type ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace, events; + + event = arguments[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + var events = jQuery.data(this, "events"), handlers = events[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, arguments ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) { + event.which = event.charCode || event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); + }, + + remove: function( handleObj ) { + var remove = true, + type = handleObj.origType.replace(rnamespaces, ""); + + jQuery.each( jQuery.data(this, "events").live || [], function() { + if ( type === this.origType.replace(rnamespaces, "") ) { + remove = false; + return false; + } + }); + + if ( remove ) { + jQuery.event.remove( this, handleObj.origType, liveHandler ); + } + } + + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( this.setInterval ) { + this.onbeforeunload = eventHandle; + } + + return false; + }, + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +var removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + elem.removeEventListener( type, handle, false ); + } : + function( elem, type, handle ) { + elem.detachEvent( "on" + type, handle ); + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = now(); + + // Mark it as fixed + this[ expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + } + // otherwise set the returnValue property of the original event to false (IE) + e.returnValue = false; + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var formElems = /textarea|input|select/i, + + changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information/focus[in] is not needed anymore + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return formElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return formElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + this.addEventListener( orig, handler, true ); + }, + teardown: function() { + this.removeEventListener( orig, handler, true ); + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.handle.call( this, e ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + context.each(function(){ + jQuery.event.add( this, liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + }); + + } else { + // unbind live handler + context.unbind( liveConvert( type, selector ), fn ); + } + } + + return this; + } +}); + +function liveHandler( event ) { + var stop, elems = [], selectors = [], args = arguments, + related, match, handleObj, elem, j, i, l, data, + events = jQuery.data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( match[i].selector === handleObj.selector ) { + elem = match[i].elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) { + stop = false; + break; + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + window.attachEvent("onunload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ -(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

      ";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
      ";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
      ").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
      ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); \ No newline at end of file +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ + return "\\" + (num - 0 + 1); + })); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

      "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
      "; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = getText; +jQuery.isXMLDoc = isXML; +jQuery.contains = contains; + +return; + +window.Sizzle = Sizzle; + +})(); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + slice = Array.prototype.slice; + +// Implement the identical functionality for filter and not +var winnow = function( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + if ( jQuery.isArray( selectors ) ) { + var ret = [], cur = this[0], match, matches = {}, selector; + + if ( cur && selectors.length ) { + for ( var i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur }); + delete matches[selector]; + } + } + cur = cur.parentNode; + } + } + + return ret; + } + + var pos = jQuery.expr.match.POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + return this.map(function( i, cur ) { + while ( cur && cur.ownerDocument && cur !== context ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) { + return cur; + } + cur = cur.parentNode; + } + return null; + }); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], cur = elem[dir]; + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g, + rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i, + rtagName = /<([\w:]+)/, + rtbody = /"; + }, + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
      ", "
      " ], + thead: [ 1, "", "
      " ], + tr: [ 2, "", "
      " ], + td: [ 3, "", "
      " ], + col: [ 2, "", "
      " ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and