[MERGE]
bzr revid: sma@tinyerp.com-20110926105300-72fwa60uvvdf1qgg bzr revid: sma@tinyerp.com-20111003112731-uobxur1ryqb5laxy
This commit is contained in:
commit
7e9e542418
|
@ -2,27 +2,26 @@ import common
|
|||
import controllers
|
||||
import common.dispatch
|
||||
import logging
|
||||
import optparse
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class Options(object):
|
||||
pass
|
||||
|
||||
def wsgi_postload():
|
||||
import openerp.wsgi
|
||||
import openerp
|
||||
import os
|
||||
import tempfile
|
||||
_logger.info("embedded mode")
|
||||
class Options(object):
|
||||
pass
|
||||
o = Options()
|
||||
o.dbfilter = '.*'
|
||||
o.dbfilter = openerp.tools.config['dbfilter']
|
||||
o.server_wide_modules = openerp.conf.server_wide_modules or ['web']
|
||||
o.session_storage = os.path.join(tempfile.gettempdir(), "oe-sessions")
|
||||
o.addons_path = os.path.dirname(os.path.dirname(__file__))
|
||||
o.addons_path = openerp.modules.module.ad_paths
|
||||
o.serve_static = True
|
||||
o.backend = 'local'
|
||||
|
||||
app = common.dispatch.Root(o)
|
||||
#import openerp.wsgi
|
||||
openerp.wsgi.register_wsgi_handler(app)
|
||||
|
||||
# TODO
|
||||
# if we detect that we are imported from the openerp server register common.Root() as a wsgi entry point
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"name" : "web",
|
||||
"depends" : [],
|
||||
'active': True,
|
||||
'post_load' : 'wsgi_postload',
|
||||
'js' : [
|
||||
"static/lib/datejs/globalization/en-US.js",
|
||||
"static/lib/datejs/core.js",
|
||||
|
@ -19,11 +20,13 @@
|
|||
"static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js",
|
||||
"static/lib/jquery.ui/js/jquery-ui-timepicker-addon.js",
|
||||
"static/lib/jquery.ui.notify/js/jquery.notify.js",
|
||||
"static/lib/jquery.deferred-queue/jquery.deferred-queue.js",
|
||||
"static/lib/json/json2.js",
|
||||
"static/lib/qweb/qweb2.js",
|
||||
"static/lib/underscore/underscore.js",
|
||||
"static/lib/underscore/underscore.string.js",
|
||||
"static/lib/labjs/LAB.src.js",
|
||||
"static/lib/py.parse/lib/py.js",
|
||||
"static/src/js/boot.js",
|
||||
"static/src/js/core.js",
|
||||
"static/src/js/dates.js",
|
||||
|
@ -32,6 +35,7 @@
|
|||
"static/src/js/views.js",
|
||||
"static/src/js/data.js",
|
||||
"static/src/js/data_export.js",
|
||||
"static/src/js/data_import.js",
|
||||
"static/src/js/search.js",
|
||||
"static/src/js/view_form.js",
|
||||
"static/src/js/view_list.js",
|
||||
|
@ -45,6 +49,6 @@
|
|||
"static/lib/jquery.ui.notify/css/ui.notify.css",
|
||||
"static/src/css/base.css",
|
||||
"static/src/css/data_export.css",
|
||||
"static/src/css/data_import.css",
|
||||
],
|
||||
'post_load' : 'wsgi_postload',
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import with_statement
|
|||
|
||||
import functools
|
||||
import logging
|
||||
import urllib
|
||||
import os
|
||||
import pprint
|
||||
import sys
|
||||
|
@ -13,7 +14,6 @@ import xmlrpclib
|
|||
import simplejson
|
||||
import werkzeug.datastructures
|
||||
import werkzeug.exceptions
|
||||
import werkzeug.urls
|
||||
import werkzeug.utils
|
||||
import werkzeug.wrappers
|
||||
import werkzeug.wsgi
|
||||
|
@ -21,8 +21,7 @@ import werkzeug.wsgi
|
|||
import ast
|
||||
import nonliterals
|
||||
import http
|
||||
# import backendlocal as backend
|
||||
import session as backend
|
||||
import session
|
||||
import openerplib
|
||||
|
||||
__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
|
||||
|
@ -34,7 +33,6 @@ _logger = logging.getLogger(__name__)
|
|||
# Globals (wont move into a pool)
|
||||
#-----------------------------------------------------------
|
||||
|
||||
applicationsession = {}
|
||||
addons_module = {}
|
||||
addons_manifest = {}
|
||||
controllers_class = {}
|
||||
|
@ -53,10 +51,6 @@ class WebRequest(object):
|
|||
:type request: :class:`werkzeug.wrappers.BaseRequest`
|
||||
:param config: configuration object
|
||||
|
||||
.. attribute:: applicationsession
|
||||
|
||||
an application-wide :class:`~collections.Mapping`
|
||||
|
||||
.. attribute:: httprequest
|
||||
|
||||
the original :class:`werkzeug.wrappers.Request` object provided to the
|
||||
|
@ -79,12 +73,12 @@ class WebRequest(object):
|
|||
|
||||
.. attribute:: session_id
|
||||
|
||||
opaque identifier for the :class:`backend.OpenERPSession` instance of
|
||||
opaque identifier for the :class:`session.OpenERPSession` instance of
|
||||
the current request
|
||||
|
||||
.. attribute:: session
|
||||
|
||||
:class:`~backend.OpenERPSession` instance for the current request
|
||||
:class:`~session.OpenERPSession` instance for the current request
|
||||
|
||||
.. attribute:: context
|
||||
|
||||
|
@ -95,18 +89,17 @@ class WebRequest(object):
|
|||
``bool``, indicates whether the debug mode is active on the client
|
||||
"""
|
||||
def __init__(self, request, config):
|
||||
self.applicationsession = applicationsession
|
||||
self.httprequest = request
|
||||
self.httpresponse = None
|
||||
self.httpsession = request.session
|
||||
self.config = config
|
||||
|
||||
def init(self, params):
|
||||
self.params = dict(params)
|
||||
# OpenERP session setup
|
||||
self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex
|
||||
self.session = self.httpsession.setdefault(
|
||||
self.session_id, backend.OpenERPSession(
|
||||
self.config.server_host, self.config.server_port))
|
||||
self.session = self.httpsession.setdefault(self.session_id, session.OpenERPSession())
|
||||
self.session.config = self.config
|
||||
self.context = self.params.pop('context', None)
|
||||
self.debug = self.params.pop('debug', False) != False
|
||||
|
||||
|
@ -317,9 +310,16 @@ class Root(object):
|
|||
by the server, will be filtered by this pattern
|
||||
"""
|
||||
def __init__(self, options):
|
||||
self.root = werkzeug.urls.Href('/web/webclient/home')
|
||||
self.root = '/web/webclient/home'
|
||||
self.config = options
|
||||
|
||||
if self.config.backend == 'local':
|
||||
conn = openerplib.get_connector(protocol='local')
|
||||
else:
|
||||
conn = openerplib.get_connector(hostname=self.config.server_host,
|
||||
port=self.config.server_port)
|
||||
self.config.connector = conn
|
||||
|
||||
self.session_cookie = 'sessionid'
|
||||
self.addons = {}
|
||||
|
||||
|
@ -349,13 +349,12 @@ class Root(object):
|
|||
request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
|
||||
|
||||
if request.path == '/':
|
||||
return werkzeug.utils.redirect(
|
||||
self.root(dict(request.args, debug='')), 301)(
|
||||
environ, start_response)
|
||||
params = urllib.urlencode(dict(request.args, debug=''))
|
||||
return werkzeug.utils.redirect(self.root + '?' + params, 301)(
|
||||
environ, start_response)
|
||||
elif request.path == '/mobile':
|
||||
return werkzeug.utils.redirect(
|
||||
'/web_mobile/static/src/web_mobile.html', 301)(
|
||||
environ, start_response)
|
||||
'/web_mobile/static/src/web_mobile.html', 301)(environ, start_response)
|
||||
|
||||
handler = self.find_handler(*(request.path.split('/')[1:]))
|
||||
|
||||
|
@ -383,21 +382,21 @@ class Root(object):
|
|||
static URLs to the corresponding directories
|
||||
"""
|
||||
statics = {}
|
||||
addons_path = self.config.addons_path
|
||||
if addons_path not in sys.path:
|
||||
sys.path.insert(0, addons_path)
|
||||
for module in os.listdir(addons_path):
|
||||
if module not in addons_module:
|
||||
manifest_path = os.path.join(addons_path, module, '__openerp__.py')
|
||||
if os.path.isfile(manifest_path):
|
||||
manifest = ast.literal_eval(open(manifest_path).read())
|
||||
_logger.info("Loading %s", module)
|
||||
m = __import__(module)
|
||||
addons_module[module] = m
|
||||
addons_manifest[module] = manifest
|
||||
|
||||
statics['/%s/static' % module] = \
|
||||
os.path.join(addons_path, module, 'static')
|
||||
for addons_path in self.config.addons_path:
|
||||
if addons_path not in sys.path:
|
||||
sys.path.insert(0, addons_path)
|
||||
for module in os.listdir(addons_path):
|
||||
if module not in addons_module:
|
||||
manifest_path = os.path.join(addons_path, module, '__openerp__.py')
|
||||
path_static = os.path.join(addons_path, module, 'static')
|
||||
if os.path.isfile(manifest_path) and os.path.isdir(path_static):
|
||||
manifest = ast.literal_eval(open(manifest_path).read())
|
||||
manifest['addons_path'] = addons_path
|
||||
_logger.info("Loading %s", module)
|
||||
m = __import__(module)
|
||||
addons_module[module] = m
|
||||
addons_manifest[module] = manifest
|
||||
statics['/%s/static' % module] = path_static
|
||||
for k, v in controllers_class.items():
|
||||
if k not in controllers_object:
|
||||
o = v()
|
||||
|
|
|
@ -20,6 +20,7 @@ def session(request, storage_path, session_cookie='sessionid'):
|
|||
else:
|
||||
request.session = session_store.new()
|
||||
|
||||
yield request.session
|
||||
|
||||
session_store.save(request.session)
|
||||
try:
|
||||
yield request.session
|
||||
finally:
|
||||
session_store.save(request.session)
|
||||
|
|
|
@ -41,6 +41,9 @@ class NonLiteralEncoder(simplejson.encoder.JSONEncoder):
|
|||
'__eval_context': object.get_eval_context()
|
||||
}
|
||||
raise TypeError('Could not encode unknown non-literal %s' % object)
|
||||
|
||||
_ALLOWED_KEYS = frozenset(['__ref', "__id", '__domains',
|
||||
'__contexts', '__eval_context', 'own_values'])
|
||||
|
||||
def non_literal_decoder(dct):
|
||||
""" Decodes JSON dicts into :class:`Domain` and :class:`Context` based on
|
||||
|
@ -50,6 +53,9 @@ def non_literal_decoder(dct):
|
|||
``own_values`` dict key.
|
||||
"""
|
||||
if '__ref' in dct:
|
||||
for x in dct.keys():
|
||||
if not x in _ALLOWED_KEYS:
|
||||
raise ValueError("'%s' key not allowed in non literal domain/context" % x)
|
||||
if dct['__ref'] == 'domain':
|
||||
domain = Domain(None, key=dct['__id'])
|
||||
if 'own_values' in dct:
|
||||
|
@ -231,7 +237,10 @@ class CompoundContext(BaseContext):
|
|||
|
||||
def evaluate(self, context=None):
|
||||
ctx = dict(context or {})
|
||||
ctx.update(self.get_eval_context() or {})
|
||||
eval_context = self.get_eval_context()
|
||||
if eval_context:
|
||||
eval_context = self.session.eval_context(eval_context)
|
||||
ctx.update(eval_context)
|
||||
final_context = {}
|
||||
for context_to_eval in self.contexts:
|
||||
if not isinstance(context_to_eval, (dict, BaseContext)):
|
||||
|
|
|
@ -38,6 +38,7 @@ Code repository: https://code.launchpad.net/~niv-openerp/openerp-client-lib/trun
|
|||
import xmlrpclib
|
||||
import logging
|
||||
import socket
|
||||
import sys
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
|
@ -70,6 +71,14 @@ class Connector(object):
|
|||
self.hostname = hostname
|
||||
self.port = port
|
||||
|
||||
def get_service(self, service_name):
|
||||
"""
|
||||
Returns a Service instance to allow easy manipulation of one of the services offered by the remote server.
|
||||
|
||||
:param service_name: The name of the service.
|
||||
"""
|
||||
return Service(self, service_name)
|
||||
|
||||
class XmlRPCConnector(Connector):
|
||||
"""
|
||||
A type of connector that uses the XMLRPC protocol.
|
||||
|
@ -190,6 +199,31 @@ class NetRPCConnector(Connector):
|
|||
socket.disconnect()
|
||||
return result
|
||||
|
||||
class LocalConnector(Connector):
|
||||
"""
|
||||
A type of connector that uses the XMLRPC protocol.
|
||||
"""
|
||||
PROTOCOL = 'local'
|
||||
|
||||
__logger = _getChildLogger(_logger, 'connector.local')
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def send(self, service_name, method, *args):
|
||||
import openerp
|
||||
# TODO Exception handling
|
||||
# This will be changed to be xmlrpc compatible
|
||||
# OpenERPWarning code 1
|
||||
# OpenERPException code 2
|
||||
try:
|
||||
result = openerp.netsvc.dispatch_rpc(service_name, method, args)
|
||||
except:
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
fault = xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value))
|
||||
raise fault
|
||||
return result
|
||||
|
||||
class Service(object):
|
||||
"""
|
||||
A class to execute RPC calls on a specific service of the remote server.
|
||||
|
@ -295,7 +329,7 @@ class Connection(object):
|
|||
|
||||
:param service_name: The name of the service.
|
||||
"""
|
||||
return Service(self.connector, service_name)
|
||||
return self.connector.get_service(service_name)
|
||||
|
||||
class AuthenticationError(Exception):
|
||||
"""
|
||||
|
@ -342,7 +376,7 @@ class Model(object):
|
|||
index = {}
|
||||
for r in result:
|
||||
index[r['id']] = r
|
||||
result = [index[x] for x in args[0]]
|
||||
result = [index[x] for x in args[0] if x in index]
|
||||
self.__logger.debug('result: %r', result)
|
||||
return result
|
||||
return proxy
|
||||
|
@ -363,7 +397,7 @@ class Model(object):
|
|||
records = self.read(record_ids, fields or [], context or {})
|
||||
return records
|
||||
|
||||
def get_connector(hostname, protocol="xmlrpc", port="auto"):
|
||||
def get_connector(hostname=None, protocol="xmlrpc", port="auto"):
|
||||
"""
|
||||
A shortcut method to easily create a connector to a remote server using XMLRPC or NetRPC.
|
||||
|
||||
|
@ -377,10 +411,12 @@ def get_connector(hostname, protocol="xmlrpc", port="auto"):
|
|||
return XmlRPCConnector(hostname, port)
|
||||
elif protocol == "netrpc":
|
||||
return NetRPCConnector(hostname, port)
|
||||
elif protocol == "local":
|
||||
return LocalConnector()
|
||||
else:
|
||||
raise ValueError("You must choose xmlrpc or netrpc")
|
||||
raise ValueError("You must choose xmlrpc or netrpc or local")
|
||||
|
||||
def get_connection(hostname, protocol="xmlrpc", port='auto', database=None,
|
||||
def get_connection(hostname=None, protocol="xmlrpc", port='auto', database=None,
|
||||
login=None, password=None, user_id=None):
|
||||
"""
|
||||
A shortcut method to easily create a connection to a remote OpenERP server.
|
||||
|
|
|
@ -5,6 +5,9 @@ import time
|
|||
import openerplib
|
||||
|
||||
import nonliterals
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
#----------------------------------------------------------
|
||||
# OpenERPSession RPC openerp backend access
|
||||
#----------------------------------------------------------
|
||||
|
@ -26,25 +29,26 @@ class OpenERPSession(object):
|
|||
Used to store references to non-literal domains which need to be
|
||||
round-tripped to the client browser.
|
||||
"""
|
||||
def __init__(self, server='127.0.0.1', port=8069):
|
||||
self._server = server
|
||||
self._port = port
|
||||
def __init__(self):
|
||||
self.config = None
|
||||
self._db = False
|
||||
self._uid = False
|
||||
self._login = False
|
||||
self._password = False
|
||||
self._locale = 'en_US'
|
||||
self.context = {}
|
||||
self.contexts_store = {}
|
||||
self.domains_store = {}
|
||||
self._lang = {}
|
||||
self.remote_timezone = 'utc'
|
||||
self.client_timezone = False
|
||||
|
||||
def __getstate__(self):
|
||||
state = dict(self.__dict__)
|
||||
if "config" in state:
|
||||
del state['config']
|
||||
return state
|
||||
|
||||
def build_connection(self):
|
||||
return openerplib.get_connection(hostname=self._server, port=self._port,
|
||||
database=self._db, login=self._login,
|
||||
user_id=self._uid, password=self._password)
|
||||
conn = openerplib.Connection(self.config.connector, database=self._db, login=self._login,
|
||||
user_id=self._uid, password=self._password)
|
||||
return conn
|
||||
|
||||
def proxy(self, service):
|
||||
return self.build_connection().get_service(service)
|
||||
|
@ -99,15 +103,6 @@ class OpenERPSession(object):
|
|||
self.context = self.model('res.users').context_get(self.context)
|
||||
self.context = self.context or {}
|
||||
|
||||
self.client_timezone = self.context.get("tz", False)
|
||||
# invalid code, anyway we decided the server will be in UTC
|
||||
#if self.client_timezone:
|
||||
# self.remote_timezone = self.execute('common', 'timezone_get')
|
||||
|
||||
self._locale = self.context.get('lang','en_US')
|
||||
lang_ids = self.execute('res.lang','search', [('code', '=', self._locale)])
|
||||
if lang_ids:
|
||||
self._lang = self.execute('res.lang', 'read',lang_ids[0], [])
|
||||
return self.context
|
||||
|
||||
@property
|
||||
|
|
|
@ -68,15 +68,7 @@ class Xml2Json:
|
|||
# OpenERP Web web Controllers
|
||||
#----------------------------------------------------------
|
||||
|
||||
def manifest_glob(addons_path, addons, key):
|
||||
files = []
|
||||
for addon in addons:
|
||||
globlist = openerpweb.addons_manifest.get(addon, {}).get(key, [])
|
||||
for pattern in globlist:
|
||||
for path in glob.glob(os.path.join(addons_path, addon, pattern)):
|
||||
files.append(path[len(addons_path):])
|
||||
return files
|
||||
|
||||
# TODO change into concat_file(addons,key) taking care of addons_path
|
||||
def concat_files(addons_path, file_list):
|
||||
""" Concatenate file content
|
||||
return (concat,timestamp)
|
||||
|
@ -104,7 +96,7 @@ home_template = textwrap.dedent("""<!DOCTYPE html>
|
|||
%(javascript)s
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
var c = new openerp.init();
|
||||
var c = new openerp.init(%(modules)s);
|
||||
var wc = new c.web.WebClient("oe");
|
||||
wc.start();
|
||||
});
|
||||
|
@ -116,24 +108,45 @@ home_template = textwrap.dedent("""<!DOCTYPE html>
|
|||
class WebClient(openerpweb.Controller):
|
||||
_cp_path = "/web/webclient"
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def csslist(self, req, mods='web'):
|
||||
return manifest_glob(req.config.addons_path, mods.split(','), 'css')
|
||||
def server_wide_modules(self, req):
|
||||
addons = [i for i in req.config.server_wide_modules if i in openerpweb.addons_manifest]
|
||||
return addons
|
||||
|
||||
def manifest_glob(self, req, addons, key):
|
||||
if addons==None:
|
||||
addons = self.server_wide_modules(req)
|
||||
else:
|
||||
addons = addons.split(',')
|
||||
files = []
|
||||
for addon in addons:
|
||||
manifest = openerpweb.addons_manifest.get(addon, None)
|
||||
if not manifest:
|
||||
continue
|
||||
addons_path = manifest['addons_path']
|
||||
globlist = manifest.get(key, [])
|
||||
for pattern in globlist:
|
||||
for path in glob.glob(os.path.join(addons_path, addon, pattern)):
|
||||
files.append(path[len(addons_path):])
|
||||
return files
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def jslist(self, req, mods='web'):
|
||||
return manifest_glob(req.config.addons_path, mods.split(','), 'js')
|
||||
def csslist(self, req, mods=None):
|
||||
return self.manifest_glob(req, mods, 'css')
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def jslist(self, req, mods=None):
|
||||
return self.manifest_glob(req, mods, 'js')
|
||||
|
||||
@openerpweb.httprequest
|
||||
def css(self, req, mods='web'):
|
||||
files = manifest_glob(req.config.addons_path, mods.split(','), 'css')
|
||||
def css(self, req, mods=None):
|
||||
files = self.manifest_glob(req, mods, 'css')
|
||||
content,timestamp = concat_files(req.config.addons_path, files)
|
||||
# TODO request set the Date of last modif and Etag
|
||||
return req.make_response(content, [('Content-Type', 'text/css')])
|
||||
|
||||
@openerpweb.httprequest
|
||||
def js(self, req, mods='web'):
|
||||
files = manifest_glob(req.config.addons_path, mods.split(','), 'js')
|
||||
def js(self, req, mods=None):
|
||||
files = self.manifest_glob(req, mods, 'js')
|
||||
content,timestamp = concat_files(req.config.addons_path, files)
|
||||
# TODO request set the Date of last modif and Etag
|
||||
return req.make_response(content, [('Content-Type', 'application/javascript')])
|
||||
|
@ -143,17 +156,19 @@ class WebClient(openerpweb.Controller):
|
|||
# script tags
|
||||
jslist = ['/web/webclient/js']
|
||||
if req.debug:
|
||||
jslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(req.config.addons_path, ['web'], 'js')]
|
||||
jslist = [i + '?debug=' + str(time.time()) for i in self.manifest_glob(req, None, 'js')]
|
||||
js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist])
|
||||
|
||||
# css tags
|
||||
csslist = ['/web/webclient/css']
|
||||
if req.debug:
|
||||
csslist = [i + '?debug=' + str(time.time()) for i in manifest_glob(req.config.addons_path, ['web'], 'css')]
|
||||
csslist = [i + '?debug=' + str(time.time()) for i in self.manifest_glob(req, None, 'css')]
|
||||
css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist])
|
||||
|
||||
r = home_template % {
|
||||
'javascript': js,
|
||||
'css': css
|
||||
'css': css,
|
||||
'modules': simplejson.dumps(self.server_wide_modules(req)),
|
||||
}
|
||||
return r
|
||||
|
||||
|
@ -166,20 +181,21 @@ class WebClient(openerpweb.Controller):
|
|||
"grouping", "decimal_point", "thousands_sep"])
|
||||
else:
|
||||
lang_obj = None
|
||||
|
||||
|
||||
if lang.count("_") > 0:
|
||||
separator = "_"
|
||||
else:
|
||||
separator = "@"
|
||||
langs = lang.split(separator)
|
||||
langs = [separator.join(langs[:x]) for x in range(1, len(langs) + 1)]
|
||||
|
||||
|
||||
transs = {}
|
||||
for addon_name in mods:
|
||||
transl = {"messages":[]}
|
||||
transs[addon_name] = transl
|
||||
for l in langs:
|
||||
f_name = os.path.join(req.config.addons_path, addon_name, "po", l + ".po")
|
||||
addons_path = openerpweb.addons_manifest[addon_name]['addons_path']
|
||||
f_name = os.path.join(addons_path, addon_name, "po", l + ".po")
|
||||
if not os.path.exists(f_name):
|
||||
continue
|
||||
try:
|
||||
|
@ -240,7 +256,7 @@ class Database(openerpweb.Controller):
|
|||
password, db = operator.itemgetter(
|
||||
'drop_pwd', 'drop_db')(
|
||||
dict(map(operator.itemgetter('name', 'value'), fields)))
|
||||
|
||||
|
||||
try:
|
||||
return req.session.proxy("db").drop(password, db)
|
||||
except xmlrpclib.Fault, e:
|
||||
|
@ -291,7 +307,7 @@ class Session(openerpweb.Controller):
|
|||
@openerpweb.jsonrequest
|
||||
def login(self, req, db, login, password):
|
||||
req.session.login(db, login, password)
|
||||
ctx = req.session.get_context()
|
||||
ctx = req.session.get_context() if req.session._uid else {}
|
||||
|
||||
return {
|
||||
"session_id": req.session_id,
|
||||
|
@ -299,6 +315,7 @@ class Session(openerpweb.Controller):
|
|||
"context": ctx,
|
||||
"db": req.session._db
|
||||
}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def get_session_info(self, req):
|
||||
req.session.assert_valid(force=True)
|
||||
|
@ -307,6 +324,7 @@ class Session(openerpweb.Controller):
|
|||
"context": req.session.get_context() if req.session._uid else False,
|
||||
"db": req.session._db
|
||||
}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def change_password (self,req,fields):
|
||||
old_password, new_password,confirm_password = operator.itemgetter('old_pwd', 'new_password','confirm_pwd')(
|
||||
|
@ -322,6 +340,7 @@ class Session(openerpweb.Controller):
|
|||
except:
|
||||
return {'error': 'Original password incorrect, your password was not changed.', 'title': 'Change Password'}
|
||||
return {'error': 'Error, password not changed !', 'title': 'Change Password'}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def sc_list(self, req):
|
||||
return req.session.model('ir.ui.view_sc').get_sc(
|
||||
|
@ -336,13 +355,14 @@ class Session(openerpweb.Controller):
|
|||
}
|
||||
except Exception, e:
|
||||
return {"error": e, "title": "Languages"}
|
||||
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def modules(self, req):
|
||||
# TODO query server for installed web modules
|
||||
mods = []
|
||||
for name, manifest in openerpweb.addons_manifest.items():
|
||||
if name != 'web' and manifest.get('active', True):
|
||||
# TODO replace by ir.module.module installed web
|
||||
if name not in req.config.server_wide_modules and manifest.get('active', True):
|
||||
mods.append(name)
|
||||
return mods
|
||||
|
||||
|
@ -472,6 +492,9 @@ def clean_action(req, action):
|
|||
if isinstance(action.get('domain'), basestring):
|
||||
action['domain'] = eval( action['domain'], eval_ctx ) or []
|
||||
|
||||
if 'type' not in action:
|
||||
action['type'] = 'ir.actions.act_window_close'
|
||||
|
||||
if action['type'] == 'ir.actions.act_window':
|
||||
return fix_view_modes(action)
|
||||
return action
|
||||
|
@ -715,7 +738,7 @@ class DataSet(openerpweb.Controller):
|
|||
args[domain_id] = d
|
||||
if context_id and len(args) - 1 >= context_id:
|
||||
args[context_id] = c
|
||||
|
||||
|
||||
for i in xrange(len(args)):
|
||||
if isinstance(args[i], web.common.nonliterals.BaseContext):
|
||||
args[i] = req.session.eval_context(args[i])
|
||||
|
@ -963,7 +986,7 @@ class SearchView(View):
|
|||
if field.get('context'):
|
||||
field["context"] = self.parse_domain(field["context"], req.session)
|
||||
return {'fields': fields}
|
||||
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def get_filters(self, req, model):
|
||||
Model = req.session.model("ir.filters")
|
||||
|
@ -972,7 +995,7 @@ class SearchView(View):
|
|||
filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session))
|
||||
filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session))
|
||||
return filters
|
||||
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def save_filter(self, req, model, name, context_to_save, domain):
|
||||
Model = req.session.model("ir.filters")
|
||||
|
@ -1382,7 +1405,7 @@ class Reports(View):
|
|||
report_data['form'] = action['datas']['form']
|
||||
if 'ids' in action['datas']:
|
||||
report_ids = action['datas']['ids']
|
||||
|
||||
|
||||
report_id = report_srv.report(
|
||||
req.session._db, req.session._uid, req.session._password,
|
||||
action["report_name"], report_ids,
|
||||
|
@ -1394,6 +1417,7 @@ class Reports(View):
|
|||
req.session._db, req.session._uid, req.session._password, report_id)
|
||||
if report_struct["state"]:
|
||||
break
|
||||
|
||||
time.sleep(self.POLLING_DELAY)
|
||||
|
||||
report = base64.b64decode(report_struct['result'])
|
||||
|
@ -1407,3 +1431,108 @@ class Reports(View):
|
|||
('Content-Type', report_mimetype),
|
||||
('Content-Length', len(report))],
|
||||
cookies={'fileToken': int(token)})
|
||||
|
||||
|
||||
class Import(View):
|
||||
_cp_path = "/web/import"
|
||||
|
||||
def fields_get(self, req, model):
|
||||
Model = req.session.model(model)
|
||||
fields = Model.fields_get(False, req.session.eval_context(req.context))
|
||||
return fields
|
||||
|
||||
@openerpweb.httprequest
|
||||
def detect_data(self, req, csvfile, csvsep, csvdel, csvcode, jsonp):
|
||||
try:
|
||||
data = list(csv.reader(
|
||||
csvfile, quotechar=str(csvdel), delimiter=str(csvsep)))
|
||||
except csv.Error, e:
|
||||
csvfile.seek(0)
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error': {
|
||||
'message': 'Error parsing CSV file: %s' % e,
|
||||
# decodes each byte to a unicode character, which may or
|
||||
# may not be printable, but decoding will succeed.
|
||||
# Otherwise simplejson will try to decode the `str` using
|
||||
# utf-8, which is very likely to blow up on characters out
|
||||
# of the ascii range (in range [128, 256))
|
||||
'preview': csvfile.read(200).decode('iso-8859-1')}}))
|
||||
|
||||
try:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps(
|
||||
{'records': data[:10]}, encoding=csvcode))
|
||||
except UnicodeDecodeError:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({
|
||||
'message': u"Failed to decode CSV file using encoding %s, "
|
||||
u"try switching to a different encoding" % csvcode
|
||||
}))
|
||||
|
||||
@openerpweb.httprequest
|
||||
def import_data(self, req, model, csvfile, csvsep, csvdel, csvcode, jsonp,
|
||||
meta):
|
||||
modle_obj = req.session.model(model)
|
||||
skip, indices, fields = operator.itemgetter('skip', 'indices', 'fields')(
|
||||
simplejson.loads(meta))
|
||||
|
||||
error = None
|
||||
if not (csvdel and len(csvdel) == 1):
|
||||
error = u"The CSV delimiter must be a single character"
|
||||
|
||||
if not indices and fields:
|
||||
error = u"You must select at least one field to import"
|
||||
|
||||
if error:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error': {'message': error}}))
|
||||
|
||||
# skip ignored records
|
||||
data_record = itertools.islice(
|
||||
csv.reader(csvfile, quotechar=str(csvdel), delimiter=str(csvsep)),
|
||||
skip, None)
|
||||
|
||||
# if only one index, itemgetter will return an atom rather than a tuple
|
||||
if len(indices) == 1: mapper = lambda row: [row[indices[0]]]
|
||||
else: mapper = operator.itemgetter(*indices)
|
||||
|
||||
data = None
|
||||
error = None
|
||||
try:
|
||||
# decode each data row
|
||||
data = [
|
||||
[record.decode(csvcode) for record in row]
|
||||
for row in itertools.imap(mapper, data_record)
|
||||
# don't insert completely empty rows (can happen due to fields
|
||||
# filtering in case of e.g. o2m content rows)
|
||||
if any(row)
|
||||
]
|
||||
except UnicodeDecodeError:
|
||||
error = u"Failed to decode CSV file using encoding %s" % csvcode
|
||||
except csv.Error, e:
|
||||
error = u"Could not process CSV file: %s" % e
|
||||
|
||||
# If the file contains nothing,
|
||||
if not data:
|
||||
error = u"File to import is empty"
|
||||
if error:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error': {'message': error}}))
|
||||
|
||||
try:
|
||||
(code, record, message, _nope) = modle_obj.import_data(
|
||||
fields, data, 'init', '', False,
|
||||
req.session.eval_context(req.context))
|
||||
except xmlrpclib.Fault, e:
|
||||
error = {"message": u"%s, %s" % (e.faultCode, e.faultString)}
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error':error}))
|
||||
|
||||
if code != -1:
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'success':True}))
|
||||
|
||||
msg = u"Error during import: %s\n\nTrying to import record %r" % (
|
||||
message, record)
|
||||
return '<script>window.top.%s(%s);</script>' % (
|
||||
jsonp, simplejson.dumps({'error': {'message':msg}}))
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
Copyright 2011 Xavier Morel. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
of conditions and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY XAVIER MOREL ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,59 @@
|
|||
.. -*- restructuredtext -*-
|
||||
|
||||
In jQuery 1.5, jQuery has introduced a Deferred object in order to
|
||||
better handle callbacks.
|
||||
|
||||
Along with Deferred, it introduced ``jQuery.when`` which allows, among
|
||||
other things, for multiplexing deferreds (waiting on multiple
|
||||
deferreds at the same time).
|
||||
|
||||
While this is very nice if all deferreds are available at the same
|
||||
point, it doesn't really work if the resolution of a deferred can
|
||||
generate more deferreds, or for collections of deferreds coming from
|
||||
multiple sources (which may not be aware of one another).
|
||||
|
||||
Deferred.queue tries to be a solution to this. It is based on the
|
||||
principle of FIFO multiple-producers multiple-consumers tasks queues,
|
||||
such as Python's `queue.Queue`_. Any code with a reference to the
|
||||
queue can add a promise to the queue, and the queue (which is a
|
||||
promise itself) will only be resolved once all promises within are
|
||||
resolved.
|
||||
|
||||
Quickstart
|
||||
----------
|
||||
|
||||
Deferred.queue has a very simple life cycle: it is dormant when
|
||||
created, it starts working as soon as promises get added to it (via
|
||||
``.push``, or by providing them directly to its constructor), and as
|
||||
soon as the last promise in the queue is resolved it resolves itself.
|
||||
|
||||
If any promise in the queue fails, the queue itself will be rejected
|
||||
(without waiting for further promises).
|
||||
|
||||
Once a queue has been resolved or rejected, adding new promises to it
|
||||
results in an error.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
``jQuery.Deferred.queue([promises...])``
|
||||
Creates a new deferred queue. Can be primed with a series of promise
|
||||
objects.
|
||||
|
||||
``.push(promise...)``
|
||||
Adds promises to the queue. Returns the queue itself (can be
|
||||
chained).
|
||||
|
||||
If the queue has already been resolved or rejected, raises an error.
|
||||
|
||||
``.then([doneCallbacks][, failCallbacks])``
|
||||
Promise/A ``then`` method.
|
||||
|
||||
``.done(doneCallbacks)``
|
||||
jQuery ``done`` extension to promise objects
|
||||
|
||||
``.fail(failCallbacks)``
|
||||
jQuery ``fail`` extension to promise objects
|
||||
|
||||
.. _queue.Queue:
|
||||
http://docs.python.org/dev/library/queue.html
|
|
@ -0,0 +1,34 @@
|
|||
(function ($) {
|
||||
"use strict";
|
||||
$.extend($.Deferred, {
|
||||
queue: function () {
|
||||
var queueDeferred = $.Deferred();
|
||||
var promises = 0;
|
||||
|
||||
function resolve() {
|
||||
if (--promises > 0) {
|
||||
return;
|
||||
}
|
||||
setTimeout($.proxy(queueDeferred, 'resolve'), 0);
|
||||
}
|
||||
|
||||
var promise = $.extend(queueDeferred.promise(), {
|
||||
push: function () {
|
||||
if (this.isResolved() || this.isRejected()) {
|
||||
throw new Error("Can not add promises to a resolved "
|
||||
+ "or rejected promise queue");
|
||||
}
|
||||
|
||||
promises += 1;
|
||||
$.when.apply(null, arguments).then(
|
||||
resolve, $.proxy(queueDeferred, 'reject'));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
if (arguments.length) {
|
||||
promise.push.apply(promise, arguments);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
});
|
||||
})(jQuery)
|
|
@ -0,0 +1,5 @@
|
|||
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
|
||||
node: 87fb1b67d6a13f10a1a328104ee4d4b2c36801ec
|
||||
branch: default
|
||||
latesttag: 0.2
|
||||
latesttagdistance: 1
|
|
@ -0,0 +1 @@
|
|||
Parser and evaluator of Python expressions
|
|
@ -0,0 +1,14 @@
|
|||
* Parser
|
||||
since parsing expressions, try with a pratt parser
|
||||
http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
|
||||
http://effbot.org/zone/simple-top-down-parsing.htm
|
||||
|
||||
Evaluator
|
||||
---------
|
||||
|
||||
* Stop busyworking trivial binary operator
|
||||
* Make it *trivial* to build Python type-wrappers
|
||||
* Implement Python's `data model
|
||||
protocols<http://docs.python.org/reference/datamodel.html#basic-customization>`_
|
||||
for *all* supported operations, optimizations can come later
|
||||
* Automatically type-wrap everything (for now anyway)
|
|
@ -0,0 +1,546 @@
|
|||
var py = {};
|
||||
(function (exports) {
|
||||
var NUMBER = /^\d$/,
|
||||
NAME_FIRST = /^[a-zA-Z_]$/,
|
||||
NAME = /^[a-zA-Z0-9_]$/;
|
||||
|
||||
var create = function (o, props) {
|
||||
function F() {}
|
||||
F.prototype = o;
|
||||
var inst = new F;
|
||||
for(var name in props) {
|
||||
if(!props.hasOwnProperty(name)) { continue; }
|
||||
inst[name] = props[name];
|
||||
}
|
||||
return inst;
|
||||
};
|
||||
|
||||
var symbols = {};
|
||||
var comparators = {};
|
||||
var Base = {
|
||||
nud: function () { throw new Error(this.id + " undefined as prefix"); },
|
||||
led: function (led) { throw new Error(this.id + " undefined as infix"); },
|
||||
toString: function () {
|
||||
if (this.id === '(constant)' || this.id === '(number)' || this.id === '(name)' || this.id === '(string)') {
|
||||
return [this.id.slice(0, this.id.length-1), ' ', this.value, ')'].join('');
|
||||
} else if (this.id === '(end)') {
|
||||
return '(end)';
|
||||
} else if (this.id === '(comparator)' ) {
|
||||
var repr = ['(comparator', this.expressions[0]];
|
||||
for (var i=0;i<this.operators.length; ++i) {
|
||||
repr.push(this.operators[i], this.expressions[i+1]);
|
||||
}
|
||||
return repr.join(' ') + ')';
|
||||
}
|
||||
var out = [this.id, this.first, this.second, this.third]
|
||||
.filter(function (r){return r}).join(' ');
|
||||
return '(' + out + ')';
|
||||
}
|
||||
};
|
||||
function symbol(id, bp) {
|
||||
bp = bp || 0;
|
||||
var s = symbols[id];
|
||||
if (s) {
|
||||
if (bp > s.lbp) {
|
||||
s.lbp = bp;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
return symbols[id] = create(Base, {
|
||||
id: id,
|
||||
lbp: bp
|
||||
});
|
||||
}
|
||||
function constant(id) {
|
||||
symbol(id).nud = function () {
|
||||
this.id = "(constant)";
|
||||
this.value = id;
|
||||
return this;
|
||||
};
|
||||
}
|
||||
function prefix(id, bp, nud) {
|
||||
symbol(id).nud = nud || function () {
|
||||
this.first = expression(bp);
|
||||
return this
|
||||
}
|
||||
}
|
||||
function infix(id, bp, led) {
|
||||
symbol(id, bp).led = led || function (left) {
|
||||
this.first = left;
|
||||
this.second = expression(bp);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
function infixr(id, bp) {
|
||||
symbol(id, bp).led = function (left) {
|
||||
this.first = left;
|
||||
this.second = expression(bp - 1);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
function comparator(id) {
|
||||
comparators[id] = true;
|
||||
var bp = 60;
|
||||
infix(id, bp, function (left) {
|
||||
this.id = '(comparator)';
|
||||
this.operators = [id];
|
||||
this.expressions = [left, expression(bp)];
|
||||
while (token.id in comparators) {
|
||||
this.operators.push(token.id);
|
||||
advance();
|
||||
this.expressions.push(
|
||||
expression(bp));
|
||||
}
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
||||
constant('None'); constant('False'); constant('True');
|
||||
|
||||
symbol('(number)').nud = function () { return this; };
|
||||
symbol('(name)').nud = function () { return this; };
|
||||
symbol('(string)').nud = function () { return this; };
|
||||
symbol('(end)');
|
||||
|
||||
symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(',');
|
||||
symbol('else');
|
||||
|
||||
symbol('lambda', 20).nud = function () {
|
||||
this.first = [];
|
||||
if (token.id !== ':') {
|
||||
for(;;) {
|
||||
if (token.id !== '(name)') {
|
||||
throw new Error('Excepted an argument name');
|
||||
}
|
||||
this.first.push(token);
|
||||
advance();
|
||||
if (token.id !== ',') {
|
||||
break;
|
||||
}
|
||||
advance(',');
|
||||
}
|
||||
}
|
||||
advance(':');
|
||||
this.second = expression();
|
||||
return this;
|
||||
};
|
||||
infix('if', 20, function (left) {
|
||||
this.first = left;
|
||||
this.second = expression();
|
||||
advance('else');
|
||||
this.third = expression();
|
||||
return this;
|
||||
});
|
||||
|
||||
infixr('or', 30); infixr('and', 40); prefix('not', 50);
|
||||
|
||||
comparator('in'); comparator('not in');
|
||||
comparator('is'); comparator('is not');
|
||||
comparator('<'); comparator('<=');
|
||||
comparator('>'); comparator('>=');
|
||||
comparator('<>'); comparator('!='); comparator('==');
|
||||
|
||||
infix('|', 70); infix('^', 80), infix('&', 90);
|
||||
|
||||
infix('<<', 100); infix('>>', 100);
|
||||
|
||||
infix('+', 110); infix('-', 110);
|
||||
|
||||
infix('*', 120); infix('/', 120);
|
||||
infix('//', 120), infix('%', 120);
|
||||
|
||||
prefix('-', 130); prefix('+', 130); prefix('~', 130);
|
||||
|
||||
infixr('**', 140);
|
||||
|
||||
infix('.', 150, function (left) {
|
||||
if (token.id !== '(name)') {
|
||||
throw new Error('Expected attribute name, got ', token.id);
|
||||
}
|
||||
this.first = left;
|
||||
this.second = token;
|
||||
advance();
|
||||
return this;
|
||||
});
|
||||
symbol('(', 150).nud = function () {
|
||||
this.first = [];
|
||||
var comma = false;
|
||||
if (token.id !== ')') {
|
||||
while (true) {
|
||||
if (token.id === ')') {
|
||||
break;
|
||||
}
|
||||
this.first.push(expression());
|
||||
if (token.id !== ',') {
|
||||
break;
|
||||
}
|
||||
comma = true;
|
||||
advance(',');
|
||||
}
|
||||
}
|
||||
advance(')');
|
||||
if (!this.first.length || comma) {
|
||||
return this;
|
||||
} else {
|
||||
return this.first[0];
|
||||
}
|
||||
};
|
||||
symbol('(').led = function (left) {
|
||||
this.first = left;
|
||||
this.second = [];
|
||||
if (token.id !== ")") {
|
||||
for(;;) {
|
||||
this.second.push(expression());
|
||||
if (token.id !== ',') {
|
||||
break;
|
||||
}
|
||||
advance(',');
|
||||
}
|
||||
}
|
||||
advance(")");
|
||||
return this;
|
||||
|
||||
};
|
||||
infix('[', 150, function (left) {
|
||||
this.first = left;
|
||||
this.second = expression();
|
||||
advance("]");
|
||||
return this;
|
||||
});
|
||||
symbol('[').nud = function () {
|
||||
this.first = [];
|
||||
if (token.id !== ']') {
|
||||
for (;;) {
|
||||
if (token.id === ']') {
|
||||
break;
|
||||
}
|
||||
this.first.push(expression());
|
||||
if (token.id !== ',') {
|
||||
break;
|
||||
}
|
||||
advance(',');
|
||||
}
|
||||
}
|
||||
advance(']');
|
||||
return this;
|
||||
};
|
||||
|
||||
symbol('{').nud = function () {
|
||||
this.first = [];
|
||||
if (token.id !== '}') {
|
||||
for(;;) {
|
||||
if (token.id === '}') {
|
||||
break;
|
||||
}
|
||||
var key = expression();
|
||||
advance(':');
|
||||
var value = expression();
|
||||
this.first.push([key, value]);
|
||||
if (token.id !== ',') {
|
||||
break;
|
||||
}
|
||||
advance(',');
|
||||
}
|
||||
}
|
||||
advance('}');
|
||||
return this;
|
||||
};
|
||||
|
||||
var longops = {
|
||||
'*': ['*'],
|
||||
'<': ['<', '=', '>'],
|
||||
'>': ['=', '>'],
|
||||
'!': ['='],
|
||||
'=': ['='],
|
||||
'/': ['/']
|
||||
};
|
||||
function Tokenizer() {
|
||||
this.states = ['initial'];
|
||||
this.tokens = [];
|
||||
}
|
||||
Tokenizer.prototype = {
|
||||
builder: function (empty) {
|
||||
var key = this.states[0] + '_builder';
|
||||
if (empty) {
|
||||
var value = this[key];
|
||||
delete this[key];
|
||||
return value;
|
||||
} else {
|
||||
return this[key] = this[key] || [];
|
||||
}
|
||||
},
|
||||
simple: function (type) {
|
||||
this.tokens.push({type: type});
|
||||
},
|
||||
push: function (new_state) {
|
||||
this.states.push(new_state);
|
||||
},
|
||||
pop: function () {
|
||||
this.states.pop();
|
||||
},
|
||||
|
||||
feed: function (str, index) {
|
||||
var s = this.states;
|
||||
return this[s[s.length - 1]](str, index);
|
||||
},
|
||||
|
||||
initial: function (str, index) {
|
||||
var character = str[index];
|
||||
|
||||
if (character in longops) {
|
||||
var follow = longops[character];
|
||||
for(var i=0, len=follow.length; i<len; ++i) {
|
||||
if (str[index+1] === follow[i]) {
|
||||
character += follow[i];
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character === ' ') {
|
||||
return index+1;
|
||||
} else if (character === '\0') {
|
||||
this.tokens.push(symbols['(end)']);
|
||||
return index + 1
|
||||
} else if (character === '"' || character === "'") {
|
||||
this.push('string');
|
||||
return index + 1;
|
||||
} else if (NUMBER.test(character)) {
|
||||
this.push('number');
|
||||
return index;
|
||||
} else if (NAME_FIRST.test(character)) {
|
||||
this.push('name');
|
||||
return index;
|
||||
} else if (character in symbols) {
|
||||
this.tokens.push(create(symbols[character]));
|
||||
return index + 1;
|
||||
}
|
||||
throw new Error("Tokenizing failure of <<" + str + ">> at index " + index
|
||||
+ ", character [[" + character + "]]"
|
||||
+ "; parsed so far: " + this.tokens);
|
||||
},
|
||||
string: function (str, index) {
|
||||
var character = str[index];
|
||||
if (character === '"' || character === "'") {
|
||||
this.tokens.push(create(symbols['(string)'], {
|
||||
value: this.builder(true).join('')
|
||||
}));
|
||||
this.pop();
|
||||
return index + 1;
|
||||
}
|
||||
this.builder().push(character);
|
||||
return index + 1;
|
||||
},
|
||||
number: function (str, index) {
|
||||
var character = str[index];
|
||||
if (!NUMBER.test(character)) {
|
||||
this.tokens.push(create(symbols['(number)'], {
|
||||
value: parseFloat(this.builder(true).join(''))
|
||||
}));
|
||||
this.pop();
|
||||
return index;
|
||||
}
|
||||
this.builder().push(character);
|
||||
return index + 1;
|
||||
},
|
||||
name: function (str, index) {
|
||||
var character = str[index];
|
||||
if (!NAME.test(character)) {
|
||||
var name = this.builder(true).join('');
|
||||
var symbol = symbols[name];
|
||||
if (symbol) {
|
||||
if (name === 'in' && this.tokens[this.tokens.length-1].id === 'not') {
|
||||
symbol = symbols['not in'];
|
||||
this.tokens.pop();
|
||||
} else if (name === 'not' && this.tokens[this.tokens.length-1].id === 'is') {
|
||||
symbol = symbols['is not'];
|
||||
this.tokens.pop();
|
||||
}
|
||||
this.tokens.push(create(symbol));
|
||||
} else {
|
||||
this.tokens.push(create(symbols['(name)'], {
|
||||
value: name
|
||||
}));
|
||||
}
|
||||
this.pop();
|
||||
return index;
|
||||
}
|
||||
this.builder().push(character);
|
||||
return index + 1;
|
||||
}
|
||||
};
|
||||
|
||||
exports.tokenize = function tokenize(str) {
|
||||
var index = 0,
|
||||
tokenizer = new Tokenizer(str);
|
||||
str += '\0';
|
||||
|
||||
do {
|
||||
index = tokenizer.feed(str, index);
|
||||
} while (index !== str.length);
|
||||
return tokenizer.tokens;
|
||||
};
|
||||
|
||||
var token, next;
|
||||
function expression(rbp) {
|
||||
rbp = rbp || 0;
|
||||
var t = token;
|
||||
token = next();
|
||||
var left = t.nud();
|
||||
while (rbp < token.lbp) {
|
||||
t = token;
|
||||
token = next();
|
||||
left = t.led(left);
|
||||
}
|
||||
return left;
|
||||
}
|
||||
function advance(id) {
|
||||
if (id && token.id !== id) {
|
||||
throw new Error(
|
||||
'Expected "' + id + '", got "' + token.id + '"');
|
||||
}
|
||||
token = next();
|
||||
}
|
||||
|
||||
exports.object = create({}, {});
|
||||
exports.bool = function (arg) { return !!arg; };
|
||||
exports.tuple = create(exports.object, {
|
||||
__contains__: function (value) {
|
||||
for(var i=0, len=this.values.length; i<len; ++i) {
|
||||
if (this.values[i] === value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
toJSON: function () {
|
||||
return this.values;
|
||||
}
|
||||
});
|
||||
exports.list = exports.tuple;
|
||||
exports.dict = create(exports.object, {
|
||||
toJSON: function () {
|
||||
return this.values;
|
||||
}
|
||||
});
|
||||
|
||||
exports.parse = function (toks) {
|
||||
var index = 0;
|
||||
token = toks[0];
|
||||
next = function () { return toks[++index]; };
|
||||
return expression();
|
||||
};
|
||||
var evaluate_operator = function (operator, a, b) {
|
||||
switch (operator) {
|
||||
case '==': case 'is': return a === b;
|
||||
case '!=': case 'is not': return a !== b;
|
||||
case '<': return a < b;
|
||||
case '<=': return a <= b;
|
||||
case '>': return a > b;
|
||||
case '>=': return a >= b;
|
||||
case 'in':
|
||||
if (typeof b === 'string') {
|
||||
return b.indexOf(a) !== -1;
|
||||
}
|
||||
return b.__contains__(a);
|
||||
case 'not in':
|
||||
if (typeof b === 'string') {
|
||||
return b.indexOf(a) === -1;
|
||||
}
|
||||
return !b.__contains__(a);
|
||||
}
|
||||
throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
|
||||
};
|
||||
exports.evaluate = function (expr, context) {
|
||||
switch (expr.id) {
|
||||
case '(name)':
|
||||
var val = context[expr.value];
|
||||
if (val === undefined) {
|
||||
throw new Error("NameError: name '" + expr.value + "' is not defined");
|
||||
}
|
||||
return val;
|
||||
case '(string)':
|
||||
case '(number)':
|
||||
return expr.value;
|
||||
case '(constant)':
|
||||
if (expr.value === 'None')
|
||||
return null;
|
||||
else if (expr.value === 'False')
|
||||
return false;
|
||||
else if (expr.value === 'True')
|
||||
return true;
|
||||
throw new Error("SyntaxError: unknown constant '" + expr.value + "'");
|
||||
case '(comparator)':
|
||||
var result, left = exports.evaluate(expr.expressions[0], context);
|
||||
for(var i=0; i<expr.operators.length; ++i) {
|
||||
result = evaluate_operator(
|
||||
expr.operators[i],
|
||||
left,
|
||||
left = exports.evaluate(expr.expressions[i+1], context));
|
||||
if (!result) { return false; }
|
||||
}
|
||||
return true;
|
||||
case '-':
|
||||
if (expr.second) {
|
||||
throw new Error('SyntaxError: binary [-] not implemented yet');
|
||||
}
|
||||
return -(exports.evaluate(expr.first, context));
|
||||
case 'not':
|
||||
return !(exports.evaluate(expr.first, context));
|
||||
case 'and':
|
||||
return (exports.evaluate(expr.first, context)
|
||||
&& exports.evaluate(expr.second, context));
|
||||
case 'or':
|
||||
return (exports.evaluate(expr.first, context)
|
||||
|| exports.evaluate(expr.second, context));
|
||||
case '(':
|
||||
if (expr.second) {
|
||||
var fn = exports.evaluate(expr.first, context), args=[];
|
||||
for (var jj=0; jj<expr.second.length; ++jj) {
|
||||
args.push(exports.evaluate(
|
||||
expr.second[jj], context));
|
||||
}
|
||||
return fn.apply(null, args);
|
||||
}
|
||||
var tuple_exprs = expr.first,
|
||||
tuple_values = [];
|
||||
for (var j=0, len=tuple_exprs.length; j<len; ++j) {
|
||||
tuple_values.push(exports.evaluate(
|
||||
tuple_exprs[j], context));
|
||||
}
|
||||
return create(exports.tuple, {values: tuple_values});
|
||||
case '[':
|
||||
if (expr.second) {
|
||||
throw new Error('SyntaxError: indexing not implemented yet');
|
||||
}
|
||||
var list_exprs = expr.first, list_values = [];
|
||||
for (var k=0; k<list_exprs.length; ++k) {
|
||||
list_values.push(exports.evaluate(
|
||||
list_exprs[k], context));
|
||||
}
|
||||
return create(exports.list, {values: list_values});
|
||||
case '{':
|
||||
var dict_exprs = expr.first, dict_values = {};
|
||||
for(var l=0; l<dict_exprs.length; ++l) {
|
||||
dict_values[exports.evaluate(dict_exprs[l][0], context)] =
|
||||
exports.evaluate(dict_exprs[l][1], context);
|
||||
}
|
||||
return create(exports.dict, {values: dict_values});
|
||||
case '.':
|
||||
if (expr.second.id !== '(name)') {
|
||||
throw new Error('SyntaxError: ' + expr);
|
||||
}
|
||||
return exports.evaluate(expr.first, context)[expr.second.value];
|
||||
default:
|
||||
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
|
||||
}
|
||||
};
|
||||
exports.eval = function (str, context) {
|
||||
return exports.evaluate(
|
||||
exports.parse(
|
||||
exports.tokenize(
|
||||
str)),
|
||||
context);
|
||||
}
|
||||
})(typeof exports === 'undefined' ? py : exports);
|
|
@ -0,0 +1,90 @@
|
|||
var py = require('../lib/py.js'),
|
||||
assert = require('assert');
|
||||
|
||||
// Literals
|
||||
assert.strictEqual(py.eval('1'), 1);
|
||||
assert.strictEqual(py.eval('None'), null);
|
||||
assert.strictEqual(py.eval('False'), false);
|
||||
assert.strictEqual(py.eval('True'), true);
|
||||
assert.strictEqual(py.eval('"somestring"'), 'somestring');
|
||||
assert.strictEqual(py.eval("'somestring'"), 'somestring');
|
||||
assert.deepEqual(py.eval("()").toJSON(), []);
|
||||
assert.deepEqual(py.eval("[]").toJSON(), []);
|
||||
assert.deepEqual(py.eval("{}").toJSON(), {});
|
||||
assert.deepEqual(py.eval("(None, True, False, 0, 1, 'foo')").toJSON(),
|
||||
[null, true, false, 0, 1, 'foo']);
|
||||
assert.deepEqual(py.eval("[None, True, False, 0, 1, 'foo']").toJSON(),
|
||||
[null, true, false, 0, 1, 'foo']);
|
||||
assert.deepEqual(py.eval("{'foo': 1, foo: 2}", {foo: 'bar'}).toJSON(),
|
||||
{foo: 1, bar: 2});
|
||||
|
||||
// Equality tests
|
||||
assert.ok(py.eval(
|
||||
"foo == 'foo'", {foo: 'foo'}));
|
||||
// Inequality
|
||||
assert.ok(py.eval(
|
||||
"foo != bar", {foo: 'foo', bar: 'bar'}));
|
||||
|
||||
// Comparisons
|
||||
assert.ok(py.eval('3 < 5'));
|
||||
assert.ok(py.eval('5 >= 3'));
|
||||
assert.ok(py.eval('3 >= 3'));
|
||||
assert.ok(!py.eval('5 < 3'));
|
||||
assert.ok(py.eval('1 < 3 < 5'));
|
||||
assert.ok(py.eval('5 > 3 > 1'));
|
||||
assert.ok(py.eval('1 < 3 > 2 == 2 > -2 not in (0, 1, 2)'));
|
||||
// string rich comparisons
|
||||
assert.ok(py.eval(
|
||||
'date >= current', {date: '2010-06-08', current: '2010-06-05'}));
|
||||
|
||||
// Boolean operators
|
||||
assert.ok(py.eval(
|
||||
"foo == 'foo' or foo == 'bar'", {foo: 'bar'}));
|
||||
assert.ok(py.eval(
|
||||
"foo == 'foo' and bar == 'bar'", {foo: 'foo', bar: 'bar'}));
|
||||
// - lazyness, second clauses NameError if not short-circuited
|
||||
assert.ok(py.eval(
|
||||
"foo == 'foo' or bar == 'bar'", {foo: 'foo'}));
|
||||
assert.ok(!py.eval(
|
||||
"foo == 'foo' and bar == 'bar'", {foo: 'bar'}));
|
||||
|
||||
// contains (in)
|
||||
assert.ok(py.eval(
|
||||
"foo in ('foo', 'bar')", {foo: 'bar'}));
|
||||
assert.ok(py.eval('1 in (1, 2, 3, 4)'));
|
||||
assert.ok(!py.eval('1 in (2, 3, 4)'));
|
||||
assert.ok(py.eval('type in ("url",)', {type: 'url'}));
|
||||
assert.ok(!py.eval('type in ("url",)', {type: 'ur'}));
|
||||
assert.ok(py.eval('1 not in (2, 3, 4)'));
|
||||
assert.ok(py.eval('type not in ("url",)', {type: 'ur'}));
|
||||
|
||||
assert.ok(py.eval(
|
||||
"foo in ['foo', 'bar']", {foo: 'bar'}));
|
||||
// string contains
|
||||
assert.ok(py.eval('type in "view"', {type: 'view'}));
|
||||
assert.ok(!py.eval('type in "view"', {type: 'bob'}));
|
||||
assert.ok(py.eval('type in "url"', {type: 'ur'}));
|
||||
|
||||
// Literals
|
||||
assert.strictEqual(py.eval('False'), false);
|
||||
assert.strictEqual(py.eval('True'), true);
|
||||
assert.strictEqual(py.eval('None'), null);
|
||||
assert.ok(py.eval('foo == False', {foo: false}));
|
||||
assert.ok(!py.eval('foo == False', {foo: true}));
|
||||
|
||||
// conversions
|
||||
assert.strictEqual(
|
||||
py.eval('bool(date_deadline)', {bool: py.bool, date_deadline: '2008'}),
|
||||
true);
|
||||
|
||||
// getattr
|
||||
assert.ok(py.eval('foo.bar', {foo: {bar: true}}));
|
||||
assert.ok(!py.eval('foo.bar', {foo: {bar: false}}));
|
||||
|
||||
// complex expressions
|
||||
assert.ok(py.eval(
|
||||
"state=='pending' and not(date_deadline and (date_deadline < current_date))",
|
||||
{state: 'pending', date_deadline: false}));
|
||||
assert.ok(py.eval(
|
||||
"state=='pending' and not(date_deadline and (date_deadline < current_date))",
|
||||
{state: 'pending', date_deadline: '2010-05-08', current_date: '2010-05-08'}));;
|
|
@ -4,301 +4,436 @@
|
|||
// Documentation: https://github.com/edtsech/underscore.string
|
||||
// Some code is borrowed from MooTools and Alexandru Marasteanu.
|
||||
|
||||
// Version 1.1.4
|
||||
// Version 1.1.6
|
||||
|
||||
(function(){
|
||||
// ------------------------- Baseline setup ---------------------------------
|
||||
|
||||
// Establish the root object, "window" in the browser, or "global" on the server.
|
||||
var root = this;
|
||||
(function(root){
|
||||
'use strict';
|
||||
|
||||
var nativeTrim = String.prototype.trim;
|
||||
if (typeof _ != 'undefined') {
|
||||
var _reverse = _().reverse,
|
||||
_include = _.include;
|
||||
}
|
||||
|
||||
function str_repeat(i, m) {
|
||||
for (var o = []; m > 0; o[--m] = i);
|
||||
return o.join('');
|
||||
// Defining helper functions.
|
||||
|
||||
var nativeTrim = String.prototype.trim;
|
||||
|
||||
var parseNumber = function(source) { return source * 1 || 0; };
|
||||
|
||||
var strRepeat = function(i, m) {
|
||||
for (var o = []; m > 0; o[--m] = i);
|
||||
return o.join('');
|
||||
};
|
||||
|
||||
var slice = function(a){
|
||||
return Array.prototype.slice.call(a);
|
||||
};
|
||||
|
||||
var defaultToWhiteSpace = function(characters){
|
||||
if (characters) {
|
||||
return _s.escapeRegExp(characters);
|
||||
}
|
||||
return '\\s';
|
||||
};
|
||||
|
||||
var sArgs = function(method){
|
||||
return function(){
|
||||
var args = slice(arguments);
|
||||
for(var i=0; i<args.length; i++)
|
||||
args[i] = args[i] == null ? '' : '' + args[i];
|
||||
return method.apply(null, args);
|
||||
};
|
||||
};
|
||||
|
||||
// sprintf() for JavaScript 0.7-beta1
|
||||
// http://www.diveintojavascript.com/projects/javascript-sprintf
|
||||
//
|
||||
// Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
|
||||
// All rights reserved.
|
||||
|
||||
var sprintf = (function() {
|
||||
function get_type(variable) {
|
||||
return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
|
||||
}
|
||||
|
||||
function defaultToWhiteSpace(characters){
|
||||
if (characters) {
|
||||
return _s.escapeRegExp(characters);
|
||||
var str_repeat = strRepeat;
|
||||
|
||||
var str_format = function() {
|
||||
if (!str_format.cache.hasOwnProperty(arguments[0])) {
|
||||
str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
|
||||
}
|
||||
return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
|
||||
};
|
||||
|
||||
str_format.format = function(parse_tree, argv) {
|
||||
var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
|
||||
for (i = 0; i < tree_length; i++) {
|
||||
node_type = get_type(parse_tree[i]);
|
||||
if (node_type === 'string') {
|
||||
output.push(parse_tree[i]);
|
||||
}
|
||||
return '\\s';
|
||||
}
|
||||
|
||||
var _s = {
|
||||
|
||||
isBlank: function(str){
|
||||
return !!str.match(/^\s*$/);
|
||||
},
|
||||
|
||||
capitalize : function(str) {
|
||||
return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
|
||||
},
|
||||
|
||||
chop: function(str, step){
|
||||
step = step || str.length;
|
||||
var arr = [];
|
||||
for (var i = 0; i < str.length;) {
|
||||
arr.push(str.slice(i,i + step));
|
||||
i = i + step;
|
||||
else if (node_type === 'array') {
|
||||
match = parse_tree[i]; // convenience purposes only
|
||||
if (match[2]) { // keyword argument
|
||||
arg = argv[cursor];
|
||||
for (k = 0; k < match[2].length; k++) {
|
||||
if (!arg.hasOwnProperty(match[2][k])) {
|
||||
throw(sprintf('[_.sprintf] property "%s" does not exist', match[2][k]));
|
||||
}
|
||||
arg = arg[match[2][k]];
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
} else if (match[1]) { // positional argument (explicit)
|
||||
arg = argv[match[1]];
|
||||
}
|
||||
else { // positional argument (implicit)
|
||||
arg = argv[cursor++];
|
||||
}
|
||||
|
||||
clean: function(str){
|
||||
return _s.strip(str.replace(/\s+/g, ' '));
|
||||
},
|
||||
if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
|
||||
throw(sprintf('[_.sprintf] expecting number but found %s', get_type(arg)));
|
||||
}
|
||||
switch (match[8]) {
|
||||
case 'b': arg = arg.toString(2); break;
|
||||
case 'c': arg = String.fromCharCode(arg); break;
|
||||
case 'd': arg = parseInt(arg, 10); break;
|
||||
case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
|
||||
case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
|
||||
case 'o': arg = arg.toString(8); break;
|
||||
case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
|
||||
case 'u': arg = Math.abs(arg); break;
|
||||
case 'x': arg = arg.toString(16); break;
|
||||
case 'X': arg = arg.toString(16).toUpperCase(); break;
|
||||
}
|
||||
arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
|
||||
pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
|
||||
pad_length = match[6] - String(arg).length;
|
||||
pad = match[6] ? str_repeat(pad_character, pad_length) : '';
|
||||
output.push(match[5] ? arg + pad : pad + arg);
|
||||
}
|
||||
}
|
||||
return output.join('');
|
||||
};
|
||||
|
||||
count: function(str, substr){
|
||||
var count = 0, index;
|
||||
for (var i=0; i < str.length;) {
|
||||
index = str.indexOf(substr, i);
|
||||
index >= 0 && count++;
|
||||
i = i + (index >= 0 ? index : 0) + substr.length;
|
||||
}
|
||||
return count;
|
||||
},
|
||||
str_format.cache = {};
|
||||
|
||||
chars: function(str) {
|
||||
return str.split('');
|
||||
},
|
||||
|
||||
escapeHTML: function(str) {
|
||||
return String(str||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
||||
.replace(/"/g, '"').replace(/'/g, "'");
|
||||
},
|
||||
|
||||
unescapeHTML: function(str) {
|
||||
return String(str||'').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, "'");
|
||||
},
|
||||
|
||||
escapeRegExp: function(str){
|
||||
// From MooTools core 1.2.4
|
||||
return String(str||'').replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
|
||||
},
|
||||
|
||||
insert: function(str, i, substr){
|
||||
var arr = str.split('');
|
||||
arr.splice(i, 0, substr);
|
||||
return arr.join('');
|
||||
},
|
||||
|
||||
includes: function(str, needle){
|
||||
return str.indexOf(needle) !== -1;
|
||||
},
|
||||
|
||||
join: function(sep) {
|
||||
// TODO: Could this be faster by converting
|
||||
// arguments to Array and using array.join(sep)?
|
||||
sep = String(sep);
|
||||
var str = "";
|
||||
for (var i=1; i < arguments.length; i += 1) {
|
||||
str += String(arguments[i]);
|
||||
if ( i !== arguments.length-1 ) {
|
||||
str += sep;
|
||||
str_format.parse = function(fmt) {
|
||||
var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
|
||||
while (_fmt) {
|
||||
if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
|
||||
parse_tree.push(match[0]);
|
||||
}
|
||||
else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
|
||||
parse_tree.push('%');
|
||||
}
|
||||
else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
|
||||
if (match[2]) {
|
||||
arg_names |= 1;
|
||||
var field_list = [], replacement_field = match[2], field_match = [];
|
||||
if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
|
||||
field_list.push(field_match[1]);
|
||||
while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
|
||||
if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
|
||||
field_list.push(field_match[1]);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
lines: function(str) {
|
||||
return str.split("\n");
|
||||
},
|
||||
|
||||
// reverse: function(str){
|
||||
// return Array.prototype.reverse.apply(str.split('')).join('');
|
||||
// },
|
||||
|
||||
splice: function(str, i, howmany, substr){
|
||||
var arr = str.split('');
|
||||
arr.splice(i, howmany, substr);
|
||||
return arr.join('');
|
||||
},
|
||||
|
||||
startsWith: function(str, starts){
|
||||
return str.length >= starts.length && str.substring(0, starts.length) === starts;
|
||||
},
|
||||
|
||||
endsWith: function(str, ends){
|
||||
return str.length >= ends.length && str.substring(str.length - ends.length) === ends;
|
||||
},
|
||||
|
||||
succ: function(str){
|
||||
var arr = str.split('');
|
||||
arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1));
|
||||
return arr.join('');
|
||||
},
|
||||
|
||||
titleize: function(str){
|
||||
var arr = str.split(' '),
|
||||
word;
|
||||
for (var i=0; i < arr.length; i++) {
|
||||
word = arr[i].split('');
|
||||
if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase();
|
||||
i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' ';
|
||||
}
|
||||
return arr.join('');
|
||||
},
|
||||
|
||||
camelize: function(str){
|
||||
return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) {
|
||||
return chr ? chr.toUpperCase() : '';
|
||||
});
|
||||
},
|
||||
|
||||
underscored: function(str){
|
||||
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase();
|
||||
},
|
||||
|
||||
dasherize: function(str){
|
||||
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase();
|
||||
},
|
||||
|
||||
trim: function(str, characters){
|
||||
if (!characters && nativeTrim) {
|
||||
return nativeTrim.call(str);
|
||||
}
|
||||
characters = defaultToWhiteSpace(characters);
|
||||
return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), '');
|
||||
},
|
||||
|
||||
ltrim: function(str, characters){
|
||||
characters = defaultToWhiteSpace(characters);
|
||||
return str.replace(new RegExp('\^[' + characters + ']+', 'g'), '');
|
||||
},
|
||||
|
||||
rtrim: function(str, characters){
|
||||
characters = defaultToWhiteSpace(characters);
|
||||
return str.replace(new RegExp('[' + characters + ']+$', 'g'), '');
|
||||
},
|
||||
|
||||
truncate: function(str, length, truncateStr){
|
||||
truncateStr = truncateStr || '...';
|
||||
return str.slice(0,length) + truncateStr;
|
||||
},
|
||||
|
||||
words: function(str, delimiter) {
|
||||
delimiter = delimiter || " ";
|
||||
return str.split(delimiter);
|
||||
},
|
||||
|
||||
|
||||
pad: function(str, length, padStr, type) {
|
||||
|
||||
var padding = '';
|
||||
var padlen = 0;
|
||||
|
||||
if (!padStr) { padStr = ' '; }
|
||||
else if (padStr.length > 1) { padStr = padStr[0]; }
|
||||
switch(type) {
|
||||
case "right":
|
||||
padlen = (length - str.length);
|
||||
padding = str_repeat(padStr, padlen);
|
||||
str = str+padding;
|
||||
break;
|
||||
case "both":
|
||||
padlen = (length - str.length);
|
||||
padding = {
|
||||
'left' : str_repeat(padStr, Math.ceil(padlen/2)),
|
||||
'right': str_repeat(padStr, Math.floor(padlen/2))
|
||||
};
|
||||
str = padding.left+str+padding.right;
|
||||
break;
|
||||
default: // "left"
|
||||
padlen = (length - str.length);
|
||||
padding = str_repeat(padStr, padlen);;
|
||||
str = padding+str;
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
lpad: function(str, length, padStr) {
|
||||
return _s.pad(str, length, padStr);
|
||||
},
|
||||
|
||||
rpad: function(str, length, padStr) {
|
||||
return _s.pad(str, length, padStr, 'right');
|
||||
},
|
||||
|
||||
lrpad: function(str, length, padStr) {
|
||||
return _s.pad(str, length, padStr, 'both');
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Credits for this function goes to
|
||||
* http://www.diveintojavascript.com/projects/sprintf-for-javascript
|
||||
*
|
||||
* Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
|
||||
* All rights reserved.
|
||||
* */
|
||||
sprintf: function(){
|
||||
|
||||
var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = '';
|
||||
while (f) {
|
||||
if (m = /^[^\x25]+/.exec(f)) {
|
||||
o.push(m[0]);
|
||||
}
|
||||
else if (m = /^\x25{2}/.exec(f)) {
|
||||
o.push('%');
|
||||
}
|
||||
else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
|
||||
if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) {
|
||||
throw('Too few arguments.');
|
||||
}
|
||||
if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) {
|
||||
throw('Expecting number but found ' + typeof(a));
|
||||
}
|
||||
switch (m[7]) {
|
||||
case 'b': a = a.toString(2); break;
|
||||
case 'c': a = String.fromCharCode(a); break;
|
||||
case 'd': a = parseInt(a); break;
|
||||
case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
|
||||
case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
|
||||
case 'o': a = a.toString(8); break;
|
||||
case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
|
||||
case 'u': a = Math.abs(a); break;
|
||||
case 'x': a = a.toString(16); break;
|
||||
case 'X': a = a.toString(16).toUpperCase(); break;
|
||||
}
|
||||
a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a);
|
||||
c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
|
||||
x = m[5] - String(a).length - s.length;
|
||||
p = m[5] ? str_repeat(c, x) : '';
|
||||
o.push(s + (m[4] ? a + p : p + a));
|
||||
else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
|
||||
field_list.push(field_match[1]);
|
||||
}
|
||||
else {
|
||||
throw('Huh ?!');
|
||||
throw('[_.sprintf] huh?');
|
||||
}
|
||||
f = f.substring(m[0].length);
|
||||
}
|
||||
}
|
||||
return o.join('');
|
||||
else {
|
||||
throw('[_.sprintf] huh?');
|
||||
}
|
||||
match[2] = field_list;
|
||||
}
|
||||
else {
|
||||
arg_names |= 2;
|
||||
}
|
||||
if (arg_names === 3) {
|
||||
throw('[_.sprintf] mixing positional and named placeholders is not (yet) supported');
|
||||
}
|
||||
parse_tree.push(match);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw('[_.sprintf] huh?');
|
||||
}
|
||||
_fmt = _fmt.substring(match[0].length);
|
||||
}
|
||||
return parse_tree;
|
||||
};
|
||||
|
||||
// Aliases
|
||||
return str_format;
|
||||
})();
|
||||
|
||||
_s.strip = _s.trim;
|
||||
_s.lstrip = _s.ltrim;
|
||||
_s.rstrip = _s.rtrim;
|
||||
_s.center = _s.lrpad
|
||||
_s.ljust = _s.lpad
|
||||
_s.rjust = _s.rpad
|
||||
|
||||
// CommonJS module is defined
|
||||
if (typeof window === 'undefined' && typeof module !== 'undefined') {
|
||||
// Export module
|
||||
module.exports = _s;
|
||||
|
||||
// Integrate with Underscore.js
|
||||
} else if (typeof root._ !== 'undefined') {
|
||||
root._.mixin(_s);
|
||||
// Defining underscore.string
|
||||
|
||||
// Or define it
|
||||
} else {
|
||||
root._ = _s;
|
||||
}
|
||||
var _s = {
|
||||
|
||||
}());
|
||||
isBlank: sArgs(function(str){
|
||||
return (/^\s*$/).test(str);
|
||||
}),
|
||||
|
||||
stripTags: sArgs(function(str){
|
||||
return str.replace(/<\/?[^>]+>/ig, '');
|
||||
}),
|
||||
|
||||
capitalize : sArgs(function(str) {
|
||||
return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
|
||||
}),
|
||||
|
||||
chop: sArgs(function(str, step){
|
||||
step = parseNumber(step) || str.length;
|
||||
var arr = [];
|
||||
for (var i = 0; i < str.length;) {
|
||||
arr.push(str.slice(i,i + step));
|
||||
i = i + step;
|
||||
}
|
||||
return arr;
|
||||
}),
|
||||
|
||||
clean: sArgs(function(str){
|
||||
return _s.strip(str.replace(/\s+/g, ' '));
|
||||
}),
|
||||
|
||||
count: sArgs(function(str, substr){
|
||||
var count = 0, index;
|
||||
for (var i=0; i < str.length;) {
|
||||
index = str.indexOf(substr, i);
|
||||
index >= 0 && count++;
|
||||
i = i + (index >= 0 ? index : 0) + substr.length;
|
||||
}
|
||||
return count;
|
||||
}),
|
||||
|
||||
chars: sArgs(function(str) {
|
||||
return str.split('');
|
||||
}),
|
||||
|
||||
escapeHTML: sArgs(function(str) {
|
||||
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
||||
.replace(/"/g, '"').replace(/'/g, "'");
|
||||
}),
|
||||
|
||||
unescapeHTML: sArgs(function(str) {
|
||||
return str.replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&');
|
||||
}),
|
||||
|
||||
escapeRegExp: sArgs(function(str){
|
||||
// From MooTools core 1.2.4
|
||||
return str.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
|
||||
}),
|
||||
|
||||
insert: sArgs(function(str, i, substr){
|
||||
var arr = str.split('');
|
||||
arr.splice(parseNumber(i), 0, substr);
|
||||
return arr.join('');
|
||||
}),
|
||||
|
||||
includes: sArgs(function(str, needle){
|
||||
return str.indexOf(needle) !== -1;
|
||||
}),
|
||||
|
||||
include: function(obj, needle) {
|
||||
if (!_include || (/string|number/).test(typeof obj)) {
|
||||
return this.includes(obj, needle);
|
||||
} else {
|
||||
return _include(obj, needle);
|
||||
}
|
||||
},
|
||||
|
||||
join: sArgs(function(sep) {
|
||||
var args = slice(arguments);
|
||||
return args.join(args.shift());
|
||||
}),
|
||||
|
||||
lines: sArgs(function(str) {
|
||||
return str.split("\n");
|
||||
}),
|
||||
|
||||
reverse: function(obj){
|
||||
if (!_reverse || (/string|number/).test(typeof obj)) {
|
||||
return Array.prototype.reverse.apply(String(obj).split('')).join('');
|
||||
} else {
|
||||
return _reverse.call(_(obj));
|
||||
}
|
||||
},
|
||||
|
||||
splice: sArgs(function(str, i, howmany, substr){
|
||||
var arr = str.split('');
|
||||
arr.splice(parseNumber(i), parseNumber(howmany), substr);
|
||||
return arr.join('');
|
||||
}),
|
||||
|
||||
startsWith: sArgs(function(str, starts){
|
||||
return str.length >= starts.length && str.substring(0, starts.length) === starts;
|
||||
}),
|
||||
|
||||
endsWith: sArgs(function(str, ends){
|
||||
return str.length >= ends.length && str.substring(str.length - ends.length) === ends;
|
||||
}),
|
||||
|
||||
succ: sArgs(function(str){
|
||||
var arr = str.split('');
|
||||
arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1));
|
||||
return arr.join('');
|
||||
}),
|
||||
|
||||
titleize: sArgs(function(str){
|
||||
var arr = str.split(' '),
|
||||
word;
|
||||
for (var i=0; i < arr.length; i++) {
|
||||
word = arr[i].split('');
|
||||
if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase();
|
||||
i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' ';
|
||||
}
|
||||
return arr.join('');
|
||||
}),
|
||||
|
||||
camelize: sArgs(function(str){
|
||||
return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) {
|
||||
return chr ? chr.toUpperCase() : '';
|
||||
});
|
||||
}),
|
||||
|
||||
underscored: function(str){
|
||||
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase();
|
||||
},
|
||||
|
||||
dasherize: function(str){
|
||||
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase();
|
||||
},
|
||||
|
||||
trim: sArgs(function(str, characters){
|
||||
if (!characters && nativeTrim) {
|
||||
return nativeTrim.call(str);
|
||||
}
|
||||
characters = defaultToWhiteSpace(characters);
|
||||
return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), '');
|
||||
}),
|
||||
|
||||
ltrim: sArgs(function(str, characters){
|
||||
characters = defaultToWhiteSpace(characters);
|
||||
return str.replace(new RegExp('\^[' + characters + ']+', 'g'), '');
|
||||
}),
|
||||
|
||||
rtrim: sArgs(function(str, characters){
|
||||
characters = defaultToWhiteSpace(characters);
|
||||
return str.replace(new RegExp('[' + characters + ']+$', 'g'), '');
|
||||
}),
|
||||
|
||||
truncate: sArgs(function(str, length, truncateStr){
|
||||
truncateStr = truncateStr || '...';
|
||||
length = parseNumber(length);
|
||||
return str.length > length ? str.slice(0,length) + truncateStr : str;
|
||||
}),
|
||||
|
||||
words: function(str, delimiter) {
|
||||
return String(str).split(delimiter || " ");
|
||||
},
|
||||
|
||||
pad: sArgs(function(str, length, padStr, type) {
|
||||
var padding = '',
|
||||
padlen = 0;
|
||||
|
||||
length = parseNumber(length);
|
||||
|
||||
if (!padStr) { padStr = ' '; }
|
||||
else if (padStr.length > 1) { padStr = padStr.charAt(0); }
|
||||
switch(type) {
|
||||
case 'right':
|
||||
padlen = (length - str.length);
|
||||
padding = strRepeat(padStr, padlen);
|
||||
str = str+padding;
|
||||
break;
|
||||
case 'both':
|
||||
padlen = (length - str.length);
|
||||
padding = {
|
||||
'left' : strRepeat(padStr, Math.ceil(padlen/2)),
|
||||
'right': strRepeat(padStr, Math.floor(padlen/2))
|
||||
};
|
||||
str = padding.left+str+padding.right;
|
||||
break;
|
||||
default: // 'left'
|
||||
padlen = (length - str.length);
|
||||
padding = strRepeat(padStr, padlen);;
|
||||
str = padding+str;
|
||||
}
|
||||
return str;
|
||||
}),
|
||||
|
||||
lpad: function(str, length, padStr) {
|
||||
return _s.pad(str, length, padStr);
|
||||
},
|
||||
|
||||
rpad: function(str, length, padStr) {
|
||||
return _s.pad(str, length, padStr, 'right');
|
||||
},
|
||||
|
||||
lrpad: function(str, length, padStr) {
|
||||
return _s.pad(str, length, padStr, 'both');
|
||||
},
|
||||
|
||||
sprintf: sprintf,
|
||||
|
||||
vsprintf: function(fmt, argv){
|
||||
argv.unshift(fmt);
|
||||
return sprintf.apply(null, argv);
|
||||
},
|
||||
|
||||
toNumber: function(str, decimals) {
|
||||
var num = parseNumber(parseNumber(str).toFixed(parseNumber(decimals)));
|
||||
return (!(num === 0 && (str !== "0" && str !== 0))) ? num : Number.NaN;
|
||||
},
|
||||
|
||||
strRight: sArgs(function(sourceStr, sep){
|
||||
var pos = (!sep) ? -1 : sourceStr.indexOf(sep);
|
||||
return (pos != -1) ? sourceStr.slice(pos+sep.length, sourceStr.length) : sourceStr;
|
||||
}),
|
||||
|
||||
strRightBack: sArgs(function(sourceStr, sep){
|
||||
var pos = (!sep) ? -1 : sourceStr.lastIndexOf(sep);
|
||||
return (pos != -1) ? sourceStr.slice(pos+sep.length, sourceStr.length) : sourceStr;
|
||||
}),
|
||||
|
||||
strLeft: sArgs(function(sourceStr, sep){
|
||||
var pos = (!sep) ? -1 : sourceStr.indexOf(sep);
|
||||
return (pos != -1) ? sourceStr.slice(0, pos) : sourceStr;
|
||||
}),
|
||||
|
||||
strLeftBack: sArgs(function(sourceStr, sep){
|
||||
var pos = sourceStr.lastIndexOf(sep);
|
||||
return (pos != -1) ? sourceStr.slice(0, pos) : sourceStr;
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
// Aliases
|
||||
|
||||
_s.strip = _s.trim;
|
||||
_s.lstrip = _s.ltrim;
|
||||
_s.rstrip = _s.rtrim;
|
||||
_s.center = _s.lrpad;
|
||||
_s.ljust = _s.lpad;
|
||||
_s.rjust = _s.rpad;
|
||||
|
||||
// CommonJS module is defined
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
// Export module
|
||||
module.exports = _s;
|
||||
|
||||
// Integrate with Underscore.js
|
||||
} else if (typeof root._ !== 'undefined') {
|
||||
root._.mixin(_s);
|
||||
|
||||
// Or define it
|
||||
} else {
|
||||
root._ = _s;
|
||||
}
|
||||
|
||||
}(this || window));
|
||||
|
|
|
@ -125,7 +125,7 @@ body.openerp, .openerp textarea, .openerp input, .openerp select, .openerp optio
|
|||
margin-top: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.openerp .login.login_invalid .login_error_message {
|
||||
.openerp .login .login_invalid .login_error_message {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -221,17 +221,11 @@ label.error {
|
|||
height: 100%;
|
||||
background: #f0eeee;
|
||||
}
|
||||
.openerp .oe-application-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Menu */
|
||||
.openerp .sf-menu {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/*
|
||||
.sf-menu a {
|
||||
padding: 5px 5px;
|
||||
}
|
||||
*/
|
||||
|
||||
.openerp .menu {
|
||||
height: 34px;
|
||||
background: #cc4e45; /* Old browsers */
|
||||
|
@ -253,7 +247,7 @@ label.error {
|
|||
height: 20px;
|
||||
margin: 3px 2px;
|
||||
padding: 0 8px;
|
||||
|
||||
|
||||
background: #bd5e54; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #bd5e54 0%, #90322a 60%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bd5e54), color-stop(60%,#90322a)); /* Chrome,Safari4+ */
|
||||
|
@ -291,13 +285,38 @@ label.error {
|
|||
background: linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* W3C */
|
||||
/* for ie */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#5c5c5c', endColorstr='#969595',GradientType=0 ); /* IE6-9 */
|
||||
|
||||
color: #fff;
|
||||
}
|
||||
.openerp .oe-application-container {
|
||||
height: 100%;
|
||||
}
|
||||
/* Secondary Menu */
|
||||
.openerp .secondary_menu .oe_toggle_secondary_menu {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
border-left: 1px solid #282828;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
z-index: 10;
|
||||
background: transparent;
|
||||
color: white;
|
||||
text-shadow: 0 1px 0 #333;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
line-height: 14px;
|
||||
right: 0;
|
||||
}
|
||||
.openerp .secondary_menu.oe_folded .oe_toggle_secondary_menu {
|
||||
position: static;
|
||||
border-left: none;
|
||||
border-bottom: 1px solid #282828;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background: #818181;
|
||||
}
|
||||
.openerp .secondary_menu.oe_folded .oe_toggle_secondary_menu span.oe_menu_fold {
|
||||
display: none;
|
||||
}
|
||||
.openerp .secondary_menu.oe_unfolded .oe_toggle_secondary_menu span.oe_menu_unfold {
|
||||
display: none;
|
||||
}
|
||||
.openerp .secondary_menu {
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
|
@ -306,15 +325,34 @@ label.error {
|
|||
background: #5A5858;
|
||||
vertical-align: top;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.openerp .secondary_menu .menu_content {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
overflow: hidden;
|
||||
.openerp .secondary_menu.oe_folded {
|
||||
width: 20px;
|
||||
min-width: 20px;
|
||||
position: static;
|
||||
}
|
||||
.openerp .secondary_menu h3 {
|
||||
padding: 0 0 2px;
|
||||
.openerp .secondary_menu.oe_folded .oe_secondary_menu.active {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
border: 4px solid #585858;
|
||||
border: 4px solid rgba(88, 88, 88, .5);
|
||||
border-radius: 4px;
|
||||
min-width: 200px;
|
||||
}
|
||||
.openerp .secondary_menu a {
|
||||
display: block;
|
||||
padding: 0 5px 2px 5px;
|
||||
line-height: 20px;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
text-shadow: 0 1px 0 #333;
|
||||
}
|
||||
.openerp .oe_secondary_submenu {
|
||||
background: #5A5858;
|
||||
}
|
||||
.openerp .secondary_menu a.oe_secondary_menu_item {
|
||||
background: #949292; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #949292 0%, #6d6b6b 87%, #282828 99%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#949292), color-stop(87%,#6d6b6b), color-stop(99%,#282828)); /* Chrome,Safari4+ */
|
||||
|
@ -325,34 +363,13 @@ label.error {
|
|||
background: linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* W3C */
|
||||
/* for ie9 */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#5B5A5A',GradientType=0 ); /* IE6-9 */
|
||||
border: none;
|
||||
/* overriding jquery ui */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
}
|
||||
.openerp .secondary_menu h4 {
|
||||
padding: 0 0 2px 10px;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
.openerp .secondary_menu h3 span, .openerp .secondary_menu h4 span {
|
||||
left: 0 !important;
|
||||
}
|
||||
.openerp .secondary_menu a {
|
||||
display: block;
|
||||
height: 20px;
|
||||
padding: 0 5px;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px 0 #333;
|
||||
}
|
||||
.openerp .secondary_menu a.leaf:hover,
|
||||
.openerp .secondary_menu a.leaf:active,
|
||||
.openerp .secondary_menu a.leaf.active,
|
||||
.openerp .secondary_menu h4:hover,
|
||||
.openerp .secondary_menu h4:active,
|
||||
.openerp .secondary_menu h4.active {
|
||||
.openerp a.oe_secondary_submenu_item:hover,
|
||||
.openerp a.oe_secondary_submenu_item.leaf.active {
|
||||
display: block;
|
||||
background: #ffffff; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #ffffff 0%, #d8d8d8 11%, #afafaf 86%, #333333 91%, #5a5858 96%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(11%,#d8d8d8), color-stop(86%,#afafaf), color-stop(91%,#333333), color-stop(96%,#5a5858)); /* Chrome,Safari4+ */
|
||||
|
@ -361,22 +378,17 @@ label.error {
|
|||
background: -ms-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* IE10+ */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#5A5858',GradientType=0 ); /* IE6-9 */
|
||||
background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* W3C */
|
||||
/* overriding jquery ui */
|
||||
-moz-border-radius-topright: 0; -webkit-border-top-right-radius: 0; border-top-right-radius: 0;
|
||||
padding: 0 5px 2px 5px;
|
||||
line-height: 20px;
|
||||
color: #3f3d3d;
|
||||
text-decoration: none;
|
||||
text-shadow: #fff 0 1px 0;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.openerp .secondary_menu h4:hover a,
|
||||
.openerp .secondary_menu h4:active a,
|
||||
.openerp .secondary_menu h4.active a {
|
||||
color: #3f3d3d;
|
||||
text-shadow: #fff 0 1px 0;
|
||||
border: none !important;
|
||||
.openerp a.oe_secondary_submenu_item.submenu.opened span:before {
|
||||
content: "\25be";
|
||||
}
|
||||
.openerp div.submenu_accordion div.menu_content a span {
|
||||
padding-left: 20px;
|
||||
.openerp a.oe_secondary_submenu_item.submenu span:before {
|
||||
content: "\25b8";
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
@ -880,6 +892,9 @@ label.error {
|
|||
.openerp .oe_forms input.field_datetime {
|
||||
min-width: 11em;
|
||||
}
|
||||
.openerp .oe_forms.oe_frame .oe_datepicker_root {
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe_forms .button {
|
||||
color: #4c4c4c;
|
||||
white-space: nowrap;
|
||||
|
@ -892,7 +907,14 @@ label.error {
|
|||
position: absolute;
|
||||
cursor: pointer;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
top: 3px;
|
||||
}
|
||||
.openerp .oe_datepicker_root {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.openerp .oe_datepicker_root input[type="text"] {
|
||||
min-width: 160px;
|
||||
}
|
||||
.openerp .oe_input_icon_disabled {
|
||||
position: absolute;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
.openerp .oe_import_grid {
|
||||
border: none;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.openerp .oe_import_grid-header .oe_import_grid-cell {
|
||||
background: url(../img/gradientlinebg.gif) repeat-x #CCCCCC;
|
||||
border-bottom: 1px solid #E3E3E3;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
.openerp .oe_import_grid-row .oe_import_grid-cell {
|
||||
border-bottom: 1px solid #E3E3E3;
|
||||
}
|
||||
.openerp .separator.horizontal {
|
||||
font-weight: bold;
|
||||
border-bottom-width: 1px;
|
||||
margin: 6px 4px 6px 1px;
|
||||
height: 20px;
|
||||
}
|
||||
.openerp .duplicate_fld{
|
||||
background-color:#FF6666;
|
||||
}
|
||||
.openerp .select_fld{
|
||||
background: none repeat scroll 0 0 white;
|
||||
}
|
||||
.openerp .ui-autocomplete {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding-right: 20px;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 182 B |
|
@ -19,9 +19,9 @@
|
|||
/**
|
||||
* OpenERP instance constructor
|
||||
*
|
||||
* @param {Boolean} skip_init if true, skips the built-in initialization
|
||||
* @param {Array} modules list of modules to initialize
|
||||
*/
|
||||
init: function(skip_init) {
|
||||
init: function(modules) {
|
||||
var new_instance = {
|
||||
// links to the global openerp
|
||||
_openerp: openerp,
|
||||
|
@ -35,8 +35,9 @@
|
|||
web_mobile: {}
|
||||
};
|
||||
openerp.sessions[new_instance._session_id] = new_instance;
|
||||
if (!skip_init){
|
||||
openerp.web(new_instance);
|
||||
modules = modules || ["web"];
|
||||
for(var i=0; i < modules.length; i++) {
|
||||
openerp[modules[i]](new_instance);
|
||||
}
|
||||
return new_instance;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/*---------------------------------------------------------
|
||||
* OpenERP Web chrome
|
||||
*---------------------------------------------------------*/
|
||||
|
||||
openerp.web.chrome = function(openerp) {
|
||||
var QWeb = openerp.web.qweb;
|
||||
|
||||
|
@ -129,6 +128,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog#
|
|||
// Destroy widget
|
||||
this.close();
|
||||
this.$dialog.dialog('destroy');
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -218,9 +218,8 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
|
|||
this.$element.closest(".openerp")
|
||||
.removeClass("login-mode")
|
||||
.addClass("database_block");
|
||||
|
||||
|
||||
var self = this;
|
||||
|
||||
var fetch_db = this.rpc("/web/database/get_list", {}, function(result) {
|
||||
self.db_list = result.db_list;
|
||||
});
|
||||
|
@ -232,7 +231,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
|
|||
self.lang_list = result.lang_list;
|
||||
});
|
||||
$.when(fetch_db, fetch_langs).then(function () {self.do_create();});
|
||||
|
||||
|
||||
this.$element.find('#db-create').click(this.do_create);
|
||||
this.$element.find('#db-drop').click(this.do_drop);
|
||||
this.$element.find('#db-backup').click(this.do_backup);
|
||||
|
@ -254,7 +253,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
|
|||
.removeClass("database_block")
|
||||
.end()
|
||||
.empty();
|
||||
|
||||
this._super();
|
||||
},
|
||||
/**
|
||||
* Converts a .serializeArray() result into a dict. Does not bother folding
|
||||
|
@ -300,9 +299,9 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
|
|||
|
||||
var admin = result[1][0];
|
||||
setTimeout(function () {
|
||||
self.stop();
|
||||
self.widget_parent.do_login(
|
||||
info.db, admin.login, admin.password);
|
||||
self.stop();
|
||||
$.unblockUI();
|
||||
});
|
||||
});
|
||||
|
@ -399,7 +398,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
|
|||
do_restore: function() {
|
||||
var self = this;
|
||||
self.$option_id.html(QWeb.render("RestoreDB", self));
|
||||
|
||||
|
||||
self.$option_id.find("form[name=restore_db_form]").validate({
|
||||
submitHandler: function (form) {
|
||||
$.blockUI({message:'<img src="/web/static/src/img/throbber2.gif">'});
|
||||
|
@ -464,15 +463,19 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database
|
|||
|
||||
openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
|
||||
remember_creditentials: true,
|
||||
|
||||
template: "Login",
|
||||
identifier_prefix: 'oe-app-login-',
|
||||
/**
|
||||
* @constructs openerp.web.Login
|
||||
* @extends openerp.web.Widget
|
||||
*
|
||||
*
|
||||
* @param parent
|
||||
* @param element_id
|
||||
*/
|
||||
init: function(parent, element_id) {
|
||||
this._super(parent, element_id);
|
||||
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
this.has_local_storage = typeof(localStorage) != 'undefined';
|
||||
this.selected_db = null;
|
||||
this.selected_login = null;
|
||||
|
@ -487,17 +490,6 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
|
|||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
this.rpc("/web/database/get_list", {}, function(result) {
|
||||
self.db_list = result.db_list;
|
||||
self.display();
|
||||
}, function() {
|
||||
self.display();
|
||||
});
|
||||
},
|
||||
display: function() {
|
||||
var self = this;
|
||||
|
||||
this.$element.html(QWeb.render("Login", this));
|
||||
this.database = new openerp.web.Database(
|
||||
this, "oe_database", "oe_db_options");
|
||||
|
||||
|
@ -506,6 +498,17 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
|
|||
});
|
||||
|
||||
this.$element.find("form").submit(this.on_submit);
|
||||
|
||||
this.rpc("/web/database/get_list", {}, function(result) {
|
||||
var tpl = openerp.web.qweb.render('Login_dblist', {db_list: result.db_list, selected_db: self.selected_db});
|
||||
self.$element.find("input[name=db]").replaceWith(tpl)
|
||||
},
|
||||
function(error, event) {
|
||||
if (error.data.fault_code === 'AccessDenied') {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
on_login_invalid: function() {
|
||||
this.$element.closest(".openerp").addClass("login-mode");
|
||||
|
@ -574,14 +577,13 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
|
|||
/**
|
||||
* @constructs openerp.web.Header
|
||||
* @extends openerp.web.Widget
|
||||
*
|
||||
*
|
||||
* @param parent
|
||||
*/
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
this.qs = "?" + jQuery.param.querystring();
|
||||
this.$content = $();
|
||||
console.debug("initializing header with id", this.element_id);
|
||||
this.update_promise = $.Deferred().resolve();
|
||||
},
|
||||
start: function() {
|
||||
|
@ -666,7 +668,7 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
|
|||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
on_action: function(action) {
|
||||
},
|
||||
on_preferences: function(){
|
||||
|
@ -702,10 +704,11 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
|
|||
},
|
||||
Save: function(){
|
||||
var inner_viewmanager = action_manager.inner_viewmanager;
|
||||
inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save(function(){
|
||||
inner_viewmanager.start();
|
||||
inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save()
|
||||
.then(function() {
|
||||
self.dialog.stop();
|
||||
window.location.reload();
|
||||
});
|
||||
$(this).dialog('destroy')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -713,7 +716,7 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
|
|||
action_manager.appendTo(this.dialog);
|
||||
action_manager.render(this.dialog);
|
||||
},
|
||||
|
||||
|
||||
change_password :function() {
|
||||
var self = this;
|
||||
this.dialog = new openerp.web.Dialog(this,{
|
||||
|
@ -728,21 +731,13 @@ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# *
|
|||
submitHandler: function (form) {
|
||||
self.rpc("/web/session/change_password",{
|
||||
'fields': $(form).serializeArray()
|
||||
}, function(result) {
|
||||
if (result.error) {
|
||||
self.display_error(result);
|
||||
}, function(result) {
|
||||
if (result.error) {
|
||||
self.display_error(result);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (result.new_password) {
|
||||
self.session.password = result.new_password;
|
||||
var session = new openerp.web.Session(self.session.server, self.session.port);
|
||||
session.start();
|
||||
session.session_login(self.session.db, self.session.login, self.session.password)
|
||||
}
|
||||
}
|
||||
self.notification.notify("Changed Password", "Password has been changed successfully");
|
||||
self.dialog.close();
|
||||
} else {
|
||||
self.session.logout();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -766,7 +761,7 @@ openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{
|
|||
/**
|
||||
* @constructs openerp.web.Menu
|
||||
* @extends openerp.web.Widget
|
||||
*
|
||||
*
|
||||
* @param parent
|
||||
* @param element_id
|
||||
* @param secondary_menu_id
|
||||
|
@ -776,74 +771,132 @@ openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{
|
|||
this.secondary_menu_id = secondary_menu_id;
|
||||
this.$secondary_menu = $("#" + secondary_menu_id).hide();
|
||||
this.menu = false;
|
||||
this.folded = false;
|
||||
if (window.localStorage) {
|
||||
this.folded = localStorage.getItem('oe_menu_folded') === 'true';
|
||||
}
|
||||
this.float_timeout = 700;
|
||||
},
|
||||
start: function() {
|
||||
this.$secondary_menu.addClass(this.folded ? 'oe_folded' : 'oe_unfolded');
|
||||
this.reload();
|
||||
},
|
||||
reload: function() {
|
||||
this.rpc("/web/menu/load", {}, this.on_loaded);
|
||||
},
|
||||
on_loaded: function(data) {
|
||||
this.data = data;
|
||||
this.$element.html(QWeb.render("Menu", this.data));
|
||||
for (var i = 0; i < this.data.data.children.length; i++) {
|
||||
var v = { menu : this.data.data.children[i] };
|
||||
this.$secondary_menu.append(QWeb.render("Menu.secondary", v));
|
||||
}
|
||||
this.$secondary_menu.find("div.menu_accordion").accordion({
|
||||
animated : false,
|
||||
autoHeight : false,
|
||||
icons : false
|
||||
});
|
||||
this.$secondary_menu.find("div.submenu_accordion").accordion({
|
||||
animated : false,
|
||||
autoHeight : false,
|
||||
active: false,
|
||||
collapsible: true,
|
||||
header: 'h4'
|
||||
});
|
||||
|
||||
this.$element.html(QWeb.render("Menu", { widget : this }));
|
||||
this.$secondary_menu.html(QWeb.render("Menu.secondary", { widget : this }));
|
||||
this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click);
|
||||
this.$secondary_menu.find('.oe_toggle_secondary_menu').click(this.on_toggle_fold);
|
||||
},
|
||||
on_toggle_fold: function() {
|
||||
this.$secondary_menu.toggleClass('oe_folded').toggleClass('oe_unfolded');
|
||||
if (this.folded) {
|
||||
this.$secondary_menu.find('.oe_secondary_menu.active').show();
|
||||
} else {
|
||||
this.$secondary_menu.find('.oe_secondary_menu').hide();
|
||||
}
|
||||
this.folded = !this.folded;
|
||||
if (window.localStorage) {
|
||||
localStorage.setItem('oe_menu_folded', this.folded.toString());
|
||||
}
|
||||
},
|
||||
on_menu_click: function(ev, id) {
|
||||
id = id || 0;
|
||||
var $menu, $parent, $secondary;
|
||||
var $clicked_menu, manual = false;
|
||||
|
||||
if (id) {
|
||||
// We can manually activate a menu with it's id (for hash url mapping)
|
||||
$menu = this.$element.find('a[data-menu=' + id + ']');
|
||||
if (!$menu.length) {
|
||||
$menu = this.$secondary_menu.find('a[data-menu=' + id + ']');
|
||||
manual = true;
|
||||
$clicked_menu = this.$element.find('a[data-menu=' + id + ']');
|
||||
if (!$clicked_menu.length) {
|
||||
$clicked_menu = this.$secondary_menu.find('a[data-menu=' + id + ']');
|
||||
}
|
||||
} else {
|
||||
$menu = $(ev.currentTarget);
|
||||
id = $menu.data('menu');
|
||||
}
|
||||
if (this.$secondary_menu.has($menu).length) {
|
||||
$secondary = $menu.parents('.menu_accordion');
|
||||
$parent = this.$element.find('a[data-menu=' + $secondary.data('menu-parent') + ']');
|
||||
} else {
|
||||
$parent = $menu;
|
||||
$secondary = this.$secondary_menu.find('.menu_accordion[data-menu-parent=' + $menu.attr('data-menu') + ']');
|
||||
$clicked_menu = $(ev.currentTarget);
|
||||
id = $clicked_menu.data('menu');
|
||||
}
|
||||
|
||||
this.$secondary_menu.find('.menu_accordion').hide();
|
||||
// TODO: ui-accordion : collapse submenus and expand the good one
|
||||
$secondary.show();
|
||||
|
||||
if (id) {
|
||||
if (this.do_menu_click($clicked_menu, manual) && id) {
|
||||
this.session.active_id = id;
|
||||
this.rpc('/web/menu/action', {'menu_id': id},
|
||||
this.on_menu_action_loaded);
|
||||
this.rpc('/web/menu/action', {'menu_id': id}, this.on_menu_action_loaded);
|
||||
}
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
},
|
||||
do_menu_click: function($clicked_menu, manual) {
|
||||
var $sub_menu, $main_menu,
|
||||
active = $clicked_menu.is('.active'),
|
||||
sub_menu_visible = false;
|
||||
|
||||
if (this.$secondary_menu.has($clicked_menu).length) {
|
||||
$sub_menu = $clicked_menu.parents('.oe_secondary_menu');
|
||||
$main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
|
||||
} else {
|
||||
$sub_menu = this.$secondary_menu.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
|
||||
$main_menu = $clicked_menu;
|
||||
}
|
||||
|
||||
sub_menu_visible = $sub_menu.is(':visible');
|
||||
this.$secondary_menu.find('.oe_secondary_menu').hide();
|
||||
|
||||
$('.active', this.$element.add(this.$secondary_menu.show())).removeClass('active');
|
||||
$parent.addClass('active');
|
||||
$menu.addClass('active');
|
||||
$menu.parent('h4').addClass('active');
|
||||
$main_menu.add($clicked_menu).add($sub_menu).addClass('active');
|
||||
|
||||
if (this.$secondary_menu.has($menu).length) {
|
||||
return !$menu.is(".leaf");
|
||||
} else {
|
||||
return false;
|
||||
if (!(this.folded && manual)) {
|
||||
this.do_show_secondary($sub_menu, $main_menu);
|
||||
}
|
||||
|
||||
if ($main_menu != $clicked_menu) {
|
||||
if ($clicked_menu.is('.submenu')) {
|
||||
$sub_menu.find('.submenu.opened').each(function() {
|
||||
if (!$(this).next().has($clicked_menu).length && !$(this).is($clicked_menu)) {
|
||||
$(this).removeClass('opened').next().hide();
|
||||
}
|
||||
});
|
||||
$clicked_menu.toggleClass('opened').next().toggle();
|
||||
} else if ($clicked_menu.is('.leaf')) {
|
||||
$sub_menu.toggle(!this.folded);
|
||||
return true;
|
||||
}
|
||||
} else if (this.folded) {
|
||||
if (active && sub_menu_visible) {
|
||||
$sub_menu.hide();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
do_show_secondary: function($sub_menu, $main_menu) {
|
||||
var self = this;
|
||||
if (this.folded) {
|
||||
var css = $main_menu.position(),
|
||||
fold_width = this.$secondary_menu.width() + 2,
|
||||
window_width = $(window).width();
|
||||
css.top += 33;
|
||||
css.left -= Math.round(($sub_menu.width() - $main_menu.width()) / 2);
|
||||
css.left = css.left < fold_width ? fold_width : css.left;
|
||||
if ((css.left + $sub_menu.width()) > window_width) {
|
||||
delete(css.left);
|
||||
css.right = 1;
|
||||
}
|
||||
$sub_menu.css(css);
|
||||
$sub_menu.mouseenter(function() {
|
||||
clearTimeout($sub_menu.data('timeoutId'));
|
||||
}).mouseleave(function(evt) {
|
||||
var timeoutId = setTimeout(function() {
|
||||
if (self.folded) {
|
||||
$sub_menu.hide();
|
||||
}
|
||||
}, self.float_timeout);
|
||||
$sub_menu.data('timeoutId', timeoutId);
|
||||
});
|
||||
}
|
||||
$sub_menu.show();
|
||||
},
|
||||
on_menu_action_loaded: function(data) {
|
||||
var self = this;
|
||||
|
@ -860,7 +913,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
|
|||
/**
|
||||
* @constructs openerp.web.WebClient
|
||||
* @extends openerp.web.Widget
|
||||
*
|
||||
*
|
||||
* @param element_id
|
||||
*/
|
||||
init: function(element_id) {
|
||||
|
@ -883,7 +936,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
|
|||
openerp.web.Widget.prototype.notification = new openerp.web.Notification(this, "oe_notification");
|
||||
|
||||
this.header = new openerp.web.Header(this);
|
||||
this.login = new openerp.web.Login(this, "oe_login");
|
||||
this.login = new openerp.web.Login(this);
|
||||
this.header.on_logout.add(this.login.on_logout);
|
||||
this.header.on_action.add(this.on_menu_action);
|
||||
|
||||
|
@ -903,9 +956,8 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
|
|||
start: function() {
|
||||
this.header.appendTo($("#oe_header"));
|
||||
this.session.start();
|
||||
this.login.start();
|
||||
this.login.appendTo($('#oe_login'));
|
||||
this.menu.start();
|
||||
console.debug("The openerp client has been initialized.");
|
||||
},
|
||||
on_logged: function() {
|
||||
if(this.action_manager)
|
||||
|
@ -951,7 +1003,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
|
|||
self.execute_home_action(home_action[0], ds);
|
||||
})
|
||||
},
|
||||
default_home: function () {
|
||||
default_home: function () {
|
||||
},
|
||||
/**
|
||||
* Bundles the execution of the home action
|
||||
|
@ -976,7 +1028,6 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
|
|||
},
|
||||
do_url_set_hash: function(url) {
|
||||
if(!this.url_external_hashchange) {
|
||||
console.log("url set #hash to",url);
|
||||
this.url_internal_hashchange = true;
|
||||
jQuery.bbq.pushState(url);
|
||||
}
|
||||
|
@ -984,10 +1035,8 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
|
|||
on_url_hashchange: function() {
|
||||
if(this.url_internal_hashchange) {
|
||||
this.url_internal_hashchange = false;
|
||||
console.log("url jump to FLAG OFF");
|
||||
} else {
|
||||
var url = jQuery.deparam.fragment();
|
||||
console.log("url jump to",url);
|
||||
this.url_external_hashchange = true;
|
||||
this.action_manager.on_url_hashchange(url);
|
||||
this.url_external_hashchange = false;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
/*---------------------------------------------------------
|
||||
* OpenERP Web core
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
var console;
|
||||
if (!console) {
|
||||
console = {log: function () {}};
|
||||
}
|
||||
if (!console.debug) {
|
||||
console.debug = console.log;
|
||||
}
|
||||
|
@ -483,7 +486,6 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
|
|||
self.user_context = result.context;
|
||||
self.db = result.db;
|
||||
self.session_save();
|
||||
self.on_session_valid();
|
||||
return true;
|
||||
}).then(success_callback);
|
||||
},
|
||||
|
@ -772,7 +774,6 @@ openerp.web.SessionAware = openerp.web.CallbackEnabled.extend(/** @lends openerp
|
|||
* // stuff that you want to init before the rendering
|
||||
* },
|
||||
* start: function() {
|
||||
* this._super();
|
||||
* // stuff you want to make after the rendering, `this.$element` holds a correct value
|
||||
* this.$element.find(".my_button").click(/* an example of event binding * /);
|
||||
*
|
||||
|
@ -916,6 +917,8 @@ openerp.web.Widget = openerp.web.SessionAware.extend(/** @lends openerp.web.Widg
|
|||
* @returns {jQuery.Deferred}
|
||||
*/
|
||||
start: function() {
|
||||
/* The default implementation is only useful for retro-compatibility, it is
|
||||
not necessary to call it using _super() when using Widget for new components. */
|
||||
if (!this.$element) {
|
||||
var tmp = document.getElementById(this.element_id);
|
||||
this.$element = tmp ? $(tmp) : undefined;
|
||||
|
@ -923,7 +926,7 @@ openerp.web.Widget = openerp.web.SessionAware.extend(/** @lends openerp.web.Widg
|
|||
return $.Deferred().done().promise();
|
||||
},
|
||||
/**
|
||||
* Destroys the current widget, also destory all its children before destroying itself.
|
||||
* Destroys the current widget, also destroy all its children before destroying itself.
|
||||
*/
|
||||
stop: function() {
|
||||
_.each(_.clone(this.widget_children), function(el) {
|
||||
|
@ -944,7 +947,6 @@ openerp.web.Widget = openerp.web.SessionAware.extend(/** @lends openerp.web.Widg
|
|||
* If that's not the case this method will simply return `false`.
|
||||
*/
|
||||
do_action: function(action, on_finished) {
|
||||
console.log('Widget.do_action', action, on_finished);
|
||||
if (this.widget_parent) {
|
||||
return this.widget_parent.do_action(action, on_finished);
|
||||
}
|
||||
|
|
|
@ -233,6 +233,7 @@ openerp.web.StaticDataGroup = openerp.web.GrouplessDataGroup.extend( /** @lends
|
|||
});
|
||||
|
||||
openerp.web.DataSet = openerp.web.Widget.extend( /** @lends openerp.web.DataSet# */{
|
||||
identifier_prefix: "dataset",
|
||||
/**
|
||||
* DateaManagement interface between views and the collection of selected
|
||||
* OpenERP records (represents the view's state?)
|
||||
|
@ -242,16 +243,12 @@ openerp.web.DataSet = openerp.web.Widget.extend( /** @lends openerp.web.DataSet
|
|||
*
|
||||
* @param {String} model the OpenERP model this dataset will manage
|
||||
*/
|
||||
init: function(source_controller, model, context) {
|
||||
// we don't want the dataset to be a child of anything!
|
||||
this._super(null);
|
||||
this.session = source_controller ? source_controller.session : undefined;
|
||||
init: function(parent, model, context) {
|
||||
this._super(parent);
|
||||
this.model = model;
|
||||
this.context = context || {};
|
||||
this.index = null;
|
||||
},
|
||||
start: function() {
|
||||
},
|
||||
previous: function () {
|
||||
this.index -= 1;
|
||||
if (this.index < 0) {
|
||||
|
@ -549,13 +546,11 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D
|
|||
sort: this.sort(),
|
||||
offset: offset,
|
||||
limit: options.limit || false
|
||||
}, function (result) {
|
||||
}).pipe(function (result) {
|
||||
self.ids = result.ids;
|
||||
self.offset = offset;
|
||||
if (callback) {
|
||||
callback(result.records);
|
||||
}
|
||||
});
|
||||
return result.records;
|
||||
}).then(callback);
|
||||
},
|
||||
get_domain: function (other_domain) {
|
||||
if (other_domain) {
|
||||
|
@ -603,20 +598,27 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D
|
|||
});
|
||||
openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
|
||||
virtual_id_prefix: "one2many_v_id_",
|
||||
virtual_id_regex: /one2many_v_id_.*/,
|
||||
debug_mode: true,
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.reset_ids([]);
|
||||
this.last_default_get = {};
|
||||
},
|
||||
default_get: function(fields, callback) {
|
||||
return this._super(fields).then(this.on_default_get).then(callback);
|
||||
},
|
||||
on_default_get: function(res) {
|
||||
this.last_default_get = res;
|
||||
},
|
||||
create: function(data, callback, error_callback) {
|
||||
var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data};
|
||||
var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data,
|
||||
defaults: this.last_default_get};
|
||||
this.to_create.push(cached);
|
||||
this.cache.push(cached);
|
||||
this.on_change();
|
||||
var to_return = $.Deferred().then(callback);
|
||||
to_return.resolve({result: cached.id});
|
||||
return to_return.promise();
|
||||
var prom = $.Deferred().then(callback);
|
||||
setTimeout(function() {prom.resolve({result: cached.id});}, 0);
|
||||
return prom.promise();
|
||||
},
|
||||
write: function (id, data, options, callback) {
|
||||
var self = this;
|
||||
|
@ -676,7 +678,8 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
|
|||
var cached = _.detect(self.cache, function(x) {return x.id === id;});
|
||||
var created = _.detect(self.to_create, function(x) {return x.id === id;});
|
||||
if (created) {
|
||||
_.each(fields, function(x) {if (cached.values[x] === undefined) cached.values[x] = false;});
|
||||
_.each(fields, function(x) {if (cached.values[x] === undefined)
|
||||
cached.values[x] = created.defaults[x] || false;});
|
||||
} else {
|
||||
if (!cached || !_.all(fields, function(x) {return cached.values[x] !== undefined}))
|
||||
to_get.push(id);
|
||||
|
@ -715,7 +718,13 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
|
|||
return completion.promise();
|
||||
}
|
||||
});
|
||||
openerp.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/;
|
||||
|
||||
openerp.web.ReadOnlyDataSetSearch = openerp.web.DataSetSearch.extend({
|
||||
default_get: function(fields, callback) {
|
||||
return this._super(fields, callback).then(this.on_default_get);
|
||||
},
|
||||
on_default_get: function(result) {},
|
||||
create: function(data, callback, error_callback) {
|
||||
this.on_create(data);
|
||||
var to_return = $.Deferred().then(callback);
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
openerp.web.data_import = function(openerp) {
|
||||
var QWeb = openerp.web.qweb;
|
||||
/**
|
||||
* Safari does not deal well at all with raw JSON data being returned. As a
|
||||
* result, we're going to cheat by using a pseudo-jsonp: instead of getting
|
||||
* JSON data in the iframe, we're getting a ``script`` tag which consists of a
|
||||
* function call and the returned data (the json dump).
|
||||
*
|
||||
* The function is an auto-generated name bound to ``window``, which calls
|
||||
* back into the callback provided here.
|
||||
*
|
||||
* @param {Object} form the form element (DOM or jQuery) to use in the call
|
||||
* @param {Object} attributes jquery.form attributes object
|
||||
* @param {Function} callback function to call with the returned data
|
||||
*/
|
||||
function jsonp(form, attributes, callback) {
|
||||
attributes = attributes || {};
|
||||
var options = {jsonp: _.uniqueId('import_callback_')};
|
||||
window[options.jsonp] = function () {
|
||||
delete window[options.jsonp];
|
||||
callback.apply(null, arguments);
|
||||
};
|
||||
if ('data' in attributes) {
|
||||
_.extend(attributes.data, options);
|
||||
} else {
|
||||
_.extend(attributes, {data: options});
|
||||
}
|
||||
$(form).ajaxSubmit(attributes);
|
||||
}
|
||||
|
||||
openerp.web.DataImport = openerp.web.Dialog.extend({
|
||||
template: 'ImportDataView',
|
||||
dialog_title: "Import Data",
|
||||
init: function(parent, dataset){
|
||||
var self = this;
|
||||
this._super(parent, {});
|
||||
this.model = parent.model;
|
||||
this.fields = [];
|
||||
this.all_fields = [];
|
||||
this.required_fields = null;
|
||||
|
||||
var convert_fields = function (root, prefix) {
|
||||
prefix = prefix || '';
|
||||
_(root.fields).each(function (f) {
|
||||
self.all_fields.push(prefix + f.name);
|
||||
if (f.fields) {
|
||||
convert_fields(f, prefix + f.id + '/');
|
||||
}
|
||||
});
|
||||
};
|
||||
this.ready = $.Deferred.queue().then(function () {
|
||||
self.required_fields = _(self.fields).chain()
|
||||
.filter(function (field) { return field.required; })
|
||||
.pluck('name')
|
||||
.value();
|
||||
convert_fields(self);
|
||||
self.all_fields.sort();
|
||||
});
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
this._super();
|
||||
this.open({
|
||||
modal: true,
|
||||
width: '70%',
|
||||
height: 'auto',
|
||||
position: 'top',
|
||||
buttons: [
|
||||
{text: "Close", click: function() { self.stop(); }},
|
||||
{text: "Import File", click: function() { self.do_import(); }, 'class': 'oe-dialog-import-button'}
|
||||
],
|
||||
close: function(event, ui) {
|
||||
self.stop();
|
||||
}
|
||||
});
|
||||
this.toggle_import_button(false);
|
||||
this.$element.find('#csvfile').change(this.on_autodetect_data);
|
||||
this.$element.find('fieldset').change(this.on_autodetect_data);
|
||||
this.$element.find('fieldset legend').click(function() {
|
||||
$(this).next().toggle();
|
||||
});
|
||||
this.ready.push(new openerp.web.DataSet(this, this.model).call(
|
||||
'fields_get', [], function (fields) {
|
||||
self.graft_fields(fields);
|
||||
}));
|
||||
},
|
||||
graft_fields: function (fields, parent, level) {
|
||||
parent = parent || this;
|
||||
level = level || 0;
|
||||
|
||||
var self = this;
|
||||
_(fields).each(function (field, field_name) {
|
||||
var f = {
|
||||
id: field_name,
|
||||
name: field_name,
|
||||
string: field.string,
|
||||
required: field.required
|
||||
};
|
||||
|
||||
switch (field.type) {
|
||||
case 'many2many':
|
||||
case 'many2one':
|
||||
f.name += '/id';
|
||||
break;
|
||||
case 'one2many':
|
||||
f.name += '/id';
|
||||
f.fields = [];
|
||||
// only fetch sub-fields to a depth of 2 levels
|
||||
if (level < 2) {
|
||||
self.ready.push(new openerp.web.DataSet(self, field.relation).call(
|
||||
'fields_get', [], function (fields) {
|
||||
self.graft_fields(fields, f, level+1);
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
parent.fields.push(f);
|
||||
});
|
||||
},
|
||||
toggle_import_button: function (newstate) {
|
||||
this.$dialog.dialog('widget')
|
||||
.find('.oe-dialog-import-button')
|
||||
.button('option', 'disabled', !newstate);
|
||||
},
|
||||
do_import: function() {
|
||||
if(!this.$element.find('#csvfile').val()) { return; }
|
||||
var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10);
|
||||
var with_headers = this.$element.find('#file_has_headers').prop('checked');
|
||||
if (!lines_to_skip && with_headers) {
|
||||
lines_to_skip = 1;
|
||||
}
|
||||
var indices = [], fields = [];
|
||||
this.$element.find(".sel_fields").each(function(index, element) {
|
||||
var val = element.value;
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
indices.push(index);
|
||||
fields.push(val);
|
||||
});
|
||||
|
||||
jsonp(this.$element.find('#import_data'), {
|
||||
url: '/web/import/import_data',
|
||||
data: {
|
||||
model: this.model,
|
||||
meta: JSON.stringify({
|
||||
skip: lines_to_skip,
|
||||
indices: indices,
|
||||
fields: fields
|
||||
})
|
||||
}
|
||||
}, this.on_import_results);
|
||||
},
|
||||
on_autodetect_data: function() {
|
||||
if(!this.$element.find('#csvfile').val()) { return; }
|
||||
jsonp(this.$element.find('#import_data'), {
|
||||
url: '/web/import/detect_data'
|
||||
}, this.on_import_results);
|
||||
},
|
||||
on_import_results: function(results) {
|
||||
this.$element.find('#result').empty();
|
||||
var headers, result_node = this.$element.find("#result");
|
||||
|
||||
if (results['records']) {
|
||||
var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10),
|
||||
with_headers = this.$element.find('#file_has_headers').prop('checked');
|
||||
headers = with_headers ? results.records[0] : null;
|
||||
|
||||
result_node.append(QWeb.render('ImportView.result', {
|
||||
'headers': headers,
|
||||
'records': lines_to_skip ? results.records.slice(lines_to_skip)
|
||||
: with_headers ? results.records.slice(1)
|
||||
: results.records
|
||||
}));
|
||||
} else if (results['error']) {
|
||||
result_node.append(QWeb.render('ImportView.error', {
|
||||
'error': results['error']}));
|
||||
} else if (results['success']) {
|
||||
if (this.widget_parent.widget_parent.active_view == "list") {
|
||||
this.widget_parent.reload_content();
|
||||
}
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.ready.then(function () {
|
||||
var $fields = self.$element.find('.sel_fields').bind('blur', function () {
|
||||
if (this.value && !_(self.all_fields).contains(this.value)) {
|
||||
this.value = '';
|
||||
}
|
||||
}).autocomplete({
|
||||
minLength: 0,
|
||||
source: self.all_fields,
|
||||
change: self.on_check_field_values
|
||||
}).focus(function () {
|
||||
$(this).autocomplete('search');
|
||||
});
|
||||
// Column auto-detection
|
||||
_(headers).each(function (header, index) {
|
||||
var f =_(self.fields).detect(function (field) {
|
||||
// TODO: levenshtein between header and field.string
|
||||
return field.name === header || field.string.toLowerCase() === header;
|
||||
});
|
||||
if (f) {
|
||||
$fields.eq(index).val(f.name);
|
||||
}
|
||||
});
|
||||
self.on_check_field_values();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Looks through all the field selections, and tries to find if two
|
||||
* (or more) columns were matched to the same model field.
|
||||
*
|
||||
* Returns a map of the multiply-mapped fields to an array of offending
|
||||
* columns (not actually columns, but the inputs containing the same field
|
||||
* names).
|
||||
*
|
||||
* Also has the side-effect of marking the discovered inputs with the class
|
||||
* ``duplicate_fld``.
|
||||
*
|
||||
* @returns {Object<String, Array<String>>} map of duplicate field matches to same-valued inputs
|
||||
*/
|
||||
find_duplicate_fields: function() {
|
||||
// Maps values to DOM nodes, in order to discover duplicates
|
||||
var values = {}, duplicates = {};
|
||||
this.$element.find(".sel_fields").each(function(index, element) {
|
||||
var value = element.value;
|
||||
var $element = $(element).removeClass('duplicate_fld');
|
||||
if (!value) { return; }
|
||||
|
||||
if (!(value in values)) {
|
||||
values[value] = element;
|
||||
} else {
|
||||
var same_valued_field = values[value];
|
||||
if (value in duplicates) {
|
||||
duplicates[value].push(element);
|
||||
} else {
|
||||
duplicates[value] = [same_valued_field, element];
|
||||
}
|
||||
$element.add(same_valued_field).addClass('duplicate_fld');
|
||||
}
|
||||
});
|
||||
return duplicates;
|
||||
},
|
||||
on_check_field_values: function () {
|
||||
this.$element.find("#message, #msg").remove();
|
||||
|
||||
var required_valid = this.check_required();
|
||||
|
||||
var duplicates = this.find_duplicate_fields();
|
||||
if (_.isEmpty(duplicates)) {
|
||||
this.toggle_import_button(required_valid);
|
||||
} else {
|
||||
var $err = $('<div id="msg" style="color: red;">Destination fields should only be selected once, some fields are selected more than once:</div>').insertBefore(this.$element.find('#result'));
|
||||
var $dupes = $('<dl>').appendTo($err);
|
||||
_(duplicates).each(function(elements, value) {
|
||||
$('<dt>').text(value).appendTo($dupes);
|
||||
_(elements).each(function(element) {
|
||||
var cell = $(element).closest('td');
|
||||
$('<dd>').text(cell.parent().children().index(cell)).appendTo($dupes);
|
||||
});
|
||||
});
|
||||
this.toggle_import_button(false);
|
||||
}
|
||||
|
||||
},
|
||||
check_required: function() {
|
||||
if (!this.required_fields.length) { return true; }
|
||||
|
||||
var selected_fields = _(this.$element.find('.sel_fields').get()).chain()
|
||||
.pluck('value')
|
||||
.compact()
|
||||
.value();
|
||||
|
||||
var missing_fields = _.difference(this.required_fields, selected_fields);
|
||||
if (missing_fields.length) {
|
||||
this.$element.find("#result").before('<div id="message" style="color:red">*Required Fields are not selected : ' + missing_fields + '.</div>');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
stop: function() {
|
||||
$(this.$dialog).remove();
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
};
|
|
@ -82,7 +82,12 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) {
|
|||
}
|
||||
switch (descriptor.widget || descriptor.type) {
|
||||
case 'integer':
|
||||
var tmp = Number(value);
|
||||
var tmp;
|
||||
do {
|
||||
tmp = value;
|
||||
value = value.replace(openerp.web._t.database.parameters.thousands_sep, "");
|
||||
} while(tmp !== value);
|
||||
tmp = Number(value);
|
||||
if (isNaN(tmp))
|
||||
throw value + " is not a correct integer";
|
||||
return tmp;
|
||||
|
@ -113,26 +118,26 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) {
|
|||
var tmp = Date.parseExact(value, _.sprintf("%s %s", Date.CultureInfo.formatPatterns.shortDate,
|
||||
Date.CultureInfo.formatPatterns.longTime));
|
||||
if (tmp !== null)
|
||||
return tmp;
|
||||
return openerp.web.datetime_to_str(tmp);
|
||||
tmp = Date.parse(value);
|
||||
if (tmp !== null)
|
||||
return tmp;
|
||||
return openerp.web.datetime_to_str(tmp);
|
||||
throw value + " is not a valid datetime";
|
||||
case 'date':
|
||||
var tmp = Date.parseExact(value, Date.CultureInfo.formatPatterns.shortDate);
|
||||
if (tmp !== null)
|
||||
return tmp;
|
||||
return openerp.web.date_to_str(tmp);
|
||||
tmp = Date.parse(value);
|
||||
if (tmp !== null)
|
||||
return tmp;
|
||||
return openerp.web.date_to_str(tmp);
|
||||
throw value + " is not a valid date";
|
||||
case 'time':
|
||||
var tmp = Date.parseExact(value, Date.CultureInfo.formatPatterns.longTime);
|
||||
if (tmp !== null)
|
||||
return tmp;
|
||||
return openerp.web.time_to_str(tmp);
|
||||
tmp = Date.parse(value);
|
||||
if (tmp !== null)
|
||||
return tmp;
|
||||
return openerp.web.time_to_str(tmp);
|
||||
throw value + " is not a valid time";
|
||||
}
|
||||
return value;
|
||||
|
|
|
@ -327,7 +327,8 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search
|
|||
this.notification.notify("Invalid Search", "triggered from search view");
|
||||
},
|
||||
do_clear: function () {
|
||||
$('.filter_label').removeClass('enabled');
|
||||
this.$element.find('.filter_label').removeClass('enabled');
|
||||
this.enabled_filters.splice(0);
|
||||
var string = $('a.searchview_group_string');
|
||||
_.each(string, function(str){
|
||||
$(str).closest('div.searchview_group').removeClass("expanded").addClass('folded');
|
||||
|
@ -542,6 +543,7 @@ openerp.web.search.FilterGroup = openerp.web.search.Input.extend(/** @lends open
|
|||
var domains = _(this.filters).chain()
|
||||
.filter(function (filter) { return filter.is_enabled(); })
|
||||
.map(function (filter) { return filter.attrs.domain; })
|
||||
.reject(_.isEmpty)
|
||||
.value();
|
||||
|
||||
if (!domains.length) { return; }
|
||||
|
@ -720,7 +722,11 @@ openerp.web.search.NumberField = openerp.web.search.Field.extend(/** @lends open
|
|||
openerp.web.search.IntegerField = openerp.web.search.NumberField.extend(/** @lends openerp.web.search.IntegerField# */{
|
||||
error_message: "not a valid integer",
|
||||
parse: function (value) {
|
||||
return parseInt(value, 10);
|
||||
try {
|
||||
return openerp.web.parse_value(value, {'widget': 'integer'});
|
||||
} catch (e) {
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
});
|
||||
/**
|
||||
|
@ -730,7 +736,11 @@ openerp.web.search.IntegerField = openerp.web.search.NumberField.extend(/** @len
|
|||
openerp.web.search.FloatField = openerp.web.search.NumberField.extend(/** @lends openerp.web.search.FloatField# */{
|
||||
error_message: "not a valid number",
|
||||
parse: function (value) {
|
||||
return parseFloat(value);
|
||||
try {
|
||||
return openerp.web.parse_value(value, {'widget': 'float'});
|
||||
} catch (e) {
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
});
|
||||
/**
|
||||
|
@ -785,20 +795,18 @@ openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @
|
|||
* @extends openerp.web.search.DateField
|
||||
*/
|
||||
openerp.web.search.DateField = openerp.web.search.Field.extend(/** @lends openerp.web.search.DateField# */{
|
||||
/**
|
||||
* enables date picker on the HTML widgets
|
||||
*/
|
||||
template: "SearchView.date",
|
||||
start: function () {
|
||||
this._super();
|
||||
this.$element.addClass('field_date').datepicker({
|
||||
dateFormat: 'yy-mm-dd'
|
||||
});
|
||||
},
|
||||
stop: function () {
|
||||
this.$element.datepicker('destroy');
|
||||
this.datewidget = new openerp.web.DateWidget(this);
|
||||
this.datewidget.prependTo(this.$element);
|
||||
this.datewidget.$element.find("input").attr("size", 15);
|
||||
this.datewidget.$element.find("input").attr("autofocus",
|
||||
this.attrs.default_focus === '1' ? 'autofocus' : undefined);
|
||||
this.datewidget.set_value(this.defaults[this.attrs.name] || false);
|
||||
},
|
||||
get_value: function () {
|
||||
return this.$element.val();
|
||||
return this.datewidget.get_value() || null;
|
||||
}
|
||||
});
|
||||
/**
|
||||
|
@ -1113,7 +1121,7 @@ openerp.web.search.ExtendedSearchProposition.Char = openerp.web.OldWidget.extend
|
|||
}
|
||||
});
|
||||
openerp.web.search.ExtendedSearchProposition.DateTime = openerp.web.OldWidget.extend({
|
||||
template: 'SearchView.extended_search.proposition.datetime',
|
||||
template: 'SearchView.extended_search.proposition.empty',
|
||||
identifier_prefix: 'extended-search-proposition-datetime',
|
||||
operators: [
|
||||
{value: "=", text: "is equal to"},
|
||||
|
@ -1124,18 +1132,16 @@ openerp.web.search.ExtendedSearchProposition.DateTime = openerp.web.OldWidget.ex
|
|||
{value: "<=", text: "less or equal than"}
|
||||
],
|
||||
get_value: function() {
|
||||
return this.$element.val();
|
||||
return this.datewidget.get_value();
|
||||
},
|
||||
start: function() {
|
||||
this._super();
|
||||
this.$element.datetimepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
timeFormat: 'hh:mm:ss'
|
||||
});
|
||||
this.datewidget = new openerp.web.DateTimeWidget(this);
|
||||
this.datewidget.prependTo(this.$element);
|
||||
}
|
||||
});
|
||||
openerp.web.search.ExtendedSearchProposition.Date = openerp.web.OldWidget.extend({
|
||||
template: 'SearchView.extended_search.proposition.date',
|
||||
template: 'SearchView.extended_search.proposition.empty',
|
||||
identifier_prefix: 'extended-search-proposition-date',
|
||||
operators: [
|
||||
{value: "=", text: "is equal to"},
|
||||
|
@ -1146,14 +1152,12 @@ openerp.web.search.ExtendedSearchProposition.Date = openerp.web.OldWidget.extend
|
|||
{value: "<=", text: "less or equal than"}
|
||||
],
|
||||
get_value: function() {
|
||||
return this.$element.val();
|
||||
return this.datewidget.get_value();
|
||||
},
|
||||
start: function() {
|
||||
this._super();
|
||||
this.$element.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
timeFormat: 'hh:mm:ss'
|
||||
});
|
||||
this.datewidget = new openerp.web.DateWidget(this);
|
||||
this.datewidget.prependTo(this.$element);
|
||||
}
|
||||
});
|
||||
openerp.web.search.ExtendedSearchProposition.Integer = openerp.web.OldWidget.extend({
|
||||
|
@ -1168,11 +1172,11 @@ openerp.web.search.ExtendedSearchProposition.Integer = openerp.web.OldWidget.ext
|
|||
{value: "<=", text: "less or equal than"}
|
||||
],
|
||||
get_value: function() {
|
||||
var value = parseFloat(this.$element.val());
|
||||
if(value != 0 && !value) {
|
||||
try {
|
||||
return openerp.web.parse_value(this.$element.val(), {'widget': 'integer'});
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
return Math.round(value);
|
||||
}
|
||||
});
|
||||
openerp.web.search.ExtendedSearchProposition.Float = openerp.web.OldWidget.extend({
|
||||
|
@ -1187,11 +1191,11 @@ openerp.web.search.ExtendedSearchProposition.Float = openerp.web.OldWidget.exten
|
|||
{value: "<=", text: "less or equal than"}
|
||||
],
|
||||
get_value: function() {
|
||||
var value = parseFloat(this.$element.val());
|
||||
if(value != 0 && !value) {
|
||||
try {
|
||||
return openerp.web.parse_value(this.$element.val(), {'widget': 'float'});
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
openerp.web.search.ExtendedSearchProposition.Selection = openerp.web.OldWidget.extend({
|
||||
|
|
|
@ -26,7 +26,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
this.set_default_options(options);
|
||||
this.dataset = dataset;
|
||||
this.model = dataset.model;
|
||||
this.view_id = view_id;
|
||||
this.view_id = view_id || false;
|
||||
this.fields_view = {};
|
||||
this.widgets = {};
|
||||
this.widgets_counter = 0;
|
||||
|
@ -41,7 +41,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
this.has_been_loaded = $.Deferred();
|
||||
this.$form_header = null;
|
||||
this.translatable_fields = [];
|
||||
_.defaults(this.options, {"always_show_new_button": true});
|
||||
_.defaults(this.options, {"always_show_new_button": true,
|
||||
"not_interactible_on_create": false});
|
||||
},
|
||||
start: function() {
|
||||
this._super();
|
||||
|
@ -72,17 +73,25 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
_.each(this.widgets, function(w) {
|
||||
w.stop();
|
||||
});
|
||||
this._super();
|
||||
},
|
||||
reposition: function ($e) {
|
||||
this.$element = $e;
|
||||
this.on_loaded();
|
||||
},
|
||||
on_loaded: function(data) {
|
||||
var self = this;
|
||||
this.fields_view = data;
|
||||
var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
|
||||
if (data) {
|
||||
this.fields_view = data;
|
||||
var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
|
||||
|
||||
this.$element.html(QWeb.render(this.form_template, { 'frame': frame, 'view': this }));
|
||||
this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'view': this });
|
||||
}
|
||||
this.$element.html(this.rendered);
|
||||
_.each(this.widgets, function(w) {
|
||||
w.start();
|
||||
});
|
||||
this.$form_header = this.$element.find('#' + this.element_id + '_header');
|
||||
this.$form_header = this.$element.find('.oe_form_header:first');
|
||||
this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() {
|
||||
var action = $(this).data('pager-action');
|
||||
self.on_pager_action(action);
|
||||
|
@ -93,6 +102,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
this.$form_header.find('button.oe_form_button_cancel').click(this.do_cancel);
|
||||
this.$form_header.find('button.oe_form_button_new').click(this.on_button_new);
|
||||
this.$form_header.find('button.oe_form_button_duplicate').click(this.on_button_duplicate);
|
||||
this.$form_header.find('button.oe_form_button_toggle').click(function () {
|
||||
self.translatable_fields = [];
|
||||
self.widgets = {};
|
||||
self.fields = {};
|
||||
self.$form_header.find('button').unbind('click');
|
||||
self.registry = self.registry === openerp.web.form.widgets
|
||||
? openerp.web.form.readonly
|
||||
: openerp.web.form.widgets;
|
||||
self.on_loaded(self.fields_view);
|
||||
self.reload();
|
||||
});
|
||||
|
||||
if (this.options.sidebar && this.options.sidebar_id) {
|
||||
this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id);
|
||||
|
@ -196,7 +216,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
}
|
||||
},
|
||||
do_update_pager: function(hide_index) {
|
||||
var $pager = this.$element.find('#' + this.element_id + '_header div.oe_form_pager');
|
||||
var $pager = this.$form_header.find('div.oe_form_pager');
|
||||
var index = hide_index ? '-' : this.dataset.index + 1;
|
||||
$pager.find('span.oe_pager_index').html(index);
|
||||
$pager.find('span.oe_pager_count').html(this.dataset.ids.length);
|
||||
|
@ -304,9 +324,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
var def = $.Deferred();
|
||||
$.when(this.has_been_loaded).then(function() {
|
||||
if (self.can_be_discarded()) {
|
||||
self.dataset.default_get(_.keys(self.fields_view.fields)).then(self.on_record_loaded).then(function() {
|
||||
var keys = _.keys(self.fields_view.fields);
|
||||
if (keys.length) {
|
||||
self.dataset.default_get(keys).then(self.on_record_loaded).then(function() {
|
||||
def.resolve();
|
||||
});
|
||||
} else {
|
||||
self.on_record_loaded({});
|
||||
def.resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return def.promise();
|
||||
|
@ -339,7 +365,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
do_save: function(success, prepend_on_create) {
|
||||
var self = this;
|
||||
if (!this.ready) {
|
||||
return false;
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
var form_dirty = false,
|
||||
form_invalid = false,
|
||||
|
@ -361,23 +387,18 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
if (form_invalid) {
|
||||
first_invalid_field.focus();
|
||||
this.on_invalid();
|
||||
return false;
|
||||
} else if (form_dirty) {
|
||||
return $.Deferred().reject();
|
||||
} else {
|
||||
console.log("About to save", values);
|
||||
if (!this.datarecord.id) {
|
||||
return this.dataset.create(values, function(r) {
|
||||
self.on_created(r, success, prepend_on_create);
|
||||
});
|
||||
return this.dataset.create(values).pipe(function(r) {
|
||||
return self.on_created(r, undefined, prepend_on_create);
|
||||
}).then(success);
|
||||
} else {
|
||||
return this.dataset.write(this.datarecord.id, values, {}, function(r) {
|
||||
self.on_saved(r, success);
|
||||
});
|
||||
return this.dataset.write(this.datarecord.id, values, {}).pipe(function(r) {
|
||||
return self.on_saved(r);
|
||||
}).then(success);
|
||||
}
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
self.on_saved({ result: true }, success);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
},
|
||||
do_save_edit: function() {
|
||||
|
@ -401,11 +422,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
on_saved: function(r, success) {
|
||||
if (!r.result) {
|
||||
// should not happen in the server, but may happen for internal purpose
|
||||
return $.Deferred().reject();
|
||||
} else {
|
||||
if (success) {
|
||||
success(r);
|
||||
}
|
||||
this.reload();
|
||||
return $.Deferred().then(success).resolve(r);
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
@ -424,6 +444,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
on_created: function(r, success, prepend_on_create) {
|
||||
if (!r.result) {
|
||||
// should not happen in the server, but may happen for internal purpose
|
||||
return $.Deferred().reject();
|
||||
} else {
|
||||
this.datarecord.id = r.result;
|
||||
if (!prepend_on_create) {
|
||||
|
@ -438,10 +459,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
this.sidebar.attachments.do_update();
|
||||
}
|
||||
console.debug("The record has been created with id #" + this.datarecord.id);
|
||||
if (success) {
|
||||
success(_.extend(r, {created: true}));
|
||||
}
|
||||
this.reload();
|
||||
return $.Deferred().then(success).resolve(_.extend(r, {created: true}));
|
||||
}
|
||||
},
|
||||
do_search: function (domains, contexts, groupbys) {
|
||||
|
@ -471,6 +490,24 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
|
|||
get_selected_ids: function() {
|
||||
var id = this.dataset.ids[this.dataset.index];
|
||||
return id ? [id] : [];
|
||||
},
|
||||
recursive_save: function() {
|
||||
var self = this;
|
||||
return $.when(this.do_save()).pipe(function(res) {
|
||||
if (self.dataset.parent_view)
|
||||
return self.dataset.parent_view.recursive_save();
|
||||
});
|
||||
},
|
||||
is_interactible_record: function() {
|
||||
var id = this.datarecord.id;
|
||||
if (!id) {
|
||||
if (this.options.not_interactible_on_create)
|
||||
return false;
|
||||
} else if (typeof(id) === "string") {
|
||||
if(openerp.web.BufferedDataSet.virtual_id_regex.test(id))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
openerp.web.FormDialog = openerp.web.Dialog.extend({
|
||||
|
@ -618,6 +655,7 @@ openerp.web.form.compute_domain = function(expr, fields) {
|
|||
|
||||
openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.Widget# */{
|
||||
template: 'Widget',
|
||||
identifier_prefix: 'formview-widget-',
|
||||
/**
|
||||
* @constructs openerp.web.form.Widget
|
||||
* @extends openerp.web.Widget
|
||||
|
@ -631,11 +669,13 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.
|
|||
this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}');
|
||||
this.type = this.type || node.tag;
|
||||
this.element_name = this.element_name || this.type;
|
||||
this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_");
|
||||
this.element_class = [
|
||||
'formview', this.view.view_id, this.element_name,
|
||||
this.view.widgets_counter++].join("_");
|
||||
|
||||
this._super(view, this.element_id);
|
||||
this._super(view);
|
||||
|
||||
this.view.widgets[this.element_id] = this;
|
||||
this.view.widgets[this.element_class] = this;
|
||||
this.children = node.children;
|
||||
this.colspan = parseInt(node.attrs.colspan || 1, 10);
|
||||
this.decrease_max_width = 0;
|
||||
|
@ -648,12 +688,7 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.
|
|||
this.width = this.node.attrs.width;
|
||||
},
|
||||
start: function() {
|
||||
this.$element = $('#' + this.element_id);
|
||||
},
|
||||
stop: function() {
|
||||
if (this.$element) {
|
||||
this.$element.remove();
|
||||
}
|
||||
this.$element = this.view.$element.find('.' + this.element_class);
|
||||
},
|
||||
process_modifiers: function() {
|
||||
var compute_domain = openerp.web.form.compute_domain;
|
||||
|
@ -757,13 +792,24 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
|
|||
for (var i = 0; i < node.children.length; i++) {
|
||||
var n = node.children[i];
|
||||
if (n.tag == "page") {
|
||||
var page = new openerp.web.form.WidgetNotebookPage(this.view, n, this, this.pages.length);
|
||||
var page = new openerp.web.form.WidgetNotebookPage(
|
||||
this.view, n, this, this.pages.length);
|
||||
this.pages.push(page);
|
||||
}
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('> ul > li').each(function (index, tab_li) {
|
||||
var page = self.pages[index],
|
||||
id = _.uniqueId(self.element_name + '-');
|
||||
page.element_id = id;
|
||||
$(tab_li).find('a').attr('href', '#' + id);
|
||||
});
|
||||
this.$element.find('> div').each(function (index, page) {
|
||||
page.id = self.pages[index].element_id;
|
||||
});
|
||||
this.$element.tabs();
|
||||
this.view.on_button_new.add_last(this.do_select_first_visible_tab);
|
||||
},
|
||||
|
@ -785,11 +831,11 @@ openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
|
|||
this.index = index;
|
||||
this.element_name = 'page_' + index;
|
||||
this._super(view, node);
|
||||
this.element_tab_id = this.element_id + '_tab';
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element_tab = $('#' + this.element_tab_id);
|
||||
this.$element_tab = this.notebook.$element.find(
|
||||
'> ul > li:eq(' + this.index + ')');
|
||||
},
|
||||
update_dom: function() {
|
||||
if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) {
|
||||
|
@ -801,9 +847,9 @@ openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
|
||||
template: 'WidgetSeparator',
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "WidgetSeparator";
|
||||
this.orientation = node.attrs.orientation || 'horizontal';
|
||||
if (this.orientation === 'vertical') {
|
||||
this.width = '1';
|
||||
|
@ -813,9 +859,10 @@ openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
|
||||
template: 'WidgetButton',
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "WidgetButton";
|
||||
this.force_disabled = false;
|
||||
if (this.string) {
|
||||
// We don't have button key bindings in the webclient
|
||||
this.string = this.string.replace(/_/g, '');
|
||||
|
@ -827,45 +874,74 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
|
|||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.click(this.on_click);
|
||||
this.$element.find("button").click(this.on_click);
|
||||
},
|
||||
on_click: function(saved) {
|
||||
on_click: function() {
|
||||
var self = this;
|
||||
if ((!this.node.attrs.special && this.view.dirty_for_user && saved !== true) || !this.view.datarecord.id) {
|
||||
this.view.do_save(function() {
|
||||
self.on_click(true);
|
||||
});
|
||||
} else {
|
||||
if (this.node.attrs.confirm) {
|
||||
var dialog = $('<div>' + this.node.attrs.confirm + '</div>').dialog({
|
||||
this.force_disabled = true;
|
||||
this.check_disable();
|
||||
this.execute_action().always(function() {
|
||||
self.force_disabled = false;
|
||||
self.check_disable();
|
||||
});
|
||||
},
|
||||
execute_action: function() {
|
||||
var self = this;
|
||||
var exec_action = function() {
|
||||
if (self.node.attrs.confirm) {
|
||||
var def = $.Deferred();
|
||||
var dialog = $('<div>' + self.node.attrs.confirm + '</div>').dialog({
|
||||
title: 'Confirm',
|
||||
modal: true,
|
||||
buttons: {
|
||||
Ok: function() {
|
||||
self.on_confirmed();
|
||||
$(this).dialog("close");
|
||||
self.on_confirmed().then(function() {
|
||||
def.resolve();
|
||||
});
|
||||
$(self).dialog("close");
|
||||
},
|
||||
Cancel: function() {
|
||||
$(this).dialog("close");
|
||||
def.resolve();
|
||||
$(self).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
return def.promise();
|
||||
} else {
|
||||
this.on_confirmed();
|
||||
return self.on_confirmed();
|
||||
}
|
||||
};
|
||||
if ((!this.node.attrs.special && this.view.dirty_for_user) || !this.view.datarecord.id) {
|
||||
return this.view.recursive_save().pipe(exec_action);
|
||||
} else {
|
||||
return exec_action();
|
||||
}
|
||||
},
|
||||
on_confirmed: function() {
|
||||
var self = this;
|
||||
|
||||
this.view.do_execute_action(
|
||||
return this.view.do_execute_action(
|
||||
this.node.attrs, this.view.dataset, this.view.datarecord.id, function () {
|
||||
self.view.reload();
|
||||
});
|
||||
},
|
||||
update_dom: function() {
|
||||
this._super();
|
||||
this.check_disable();
|
||||
},
|
||||
check_disable: function() {
|
||||
if (this.force_disabled || !this.view.is_interactible_record()) {
|
||||
this.$element.find("button").attr("disabled", "disabled");
|
||||
this.$element.find("button").css("color", "grey");
|
||||
} else {
|
||||
this.$element.find("button").removeAttr("disabled");
|
||||
this.$element.find("button").css("color", "");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
|
||||
template: 'WidgetLabel',
|
||||
init: function(view, node) {
|
||||
this.element_name = 'label_' + node.attrs.name;
|
||||
|
||||
|
@ -876,7 +952,6 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
|
|||
this.template = "WidgetParagraph";
|
||||
this.colspan = parseInt(this.node.attrs.colspan || 1, 10);
|
||||
} else {
|
||||
this.template = "WidgetLabel";
|
||||
this.colspan = 1;
|
||||
this.width = '1%';
|
||||
this.decrease_max_width = 1;
|
||||
|
@ -895,7 +970,7 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
|
|||
var self = this;
|
||||
this.$element.find("label").dblclick(function() {
|
||||
var widget = self['for'] || self;
|
||||
console.log(widget.element_id , widget);
|
||||
console.log(widget.element_class , widget);
|
||||
window.w = widget;
|
||||
});
|
||||
}
|
||||
|
@ -960,7 +1035,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
|
|||
return !this.invalid;
|
||||
},
|
||||
is_dirty: function() {
|
||||
return this.dirty;
|
||||
return this.dirty && !this.readonly;
|
||||
},
|
||||
get_on_change_value: function() {
|
||||
return this.get_value();
|
||||
|
@ -995,37 +1070,40 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
|
|||
focus: function() {
|
||||
},
|
||||
_build_view_fields_values: function() {
|
||||
var a_dataset = this.view.dataset || {};
|
||||
var a_dataset = this.view.dataset;
|
||||
var fields_values = this.view.get_fields_values();
|
||||
var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {};
|
||||
fields_values.parent = parent_values;
|
||||
return fields_values;
|
||||
},
|
||||
_build_eval_context: function() {
|
||||
var a_dataset = this.view.dataset;
|
||||
return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values());
|
||||
},
|
||||
/**
|
||||
* Builds a new context usable for operations related to fields by merging
|
||||
* the fields'context with the action's context.
|
||||
*/
|
||||
build_context: function() {
|
||||
// I previously belevied contexts should be herrited, but now I doubt it
|
||||
//var a_context = this.view.dataset.get_context() || {};
|
||||
var f_context = this.field.context || null;
|
||||
// maybe the default_get should only be used when we do a default_get?
|
||||
var v_context1 = this.node.attrs.default_get || {};
|
||||
var v_context2 = this.node.attrs.context || {};
|
||||
var v_context = new openerp.web.CompoundContext(v_context1, v_context2);
|
||||
if (v_context1.__ref || v_context2.__ref || true) { //TODO niv: remove || true
|
||||
var fields_values = this._build_view_fields_values();
|
||||
var v_contexts = _.compact([this.node.attrs.default_get || null,
|
||||
this.node.attrs.context || null]);
|
||||
var v_context = new openerp.web.CompoundContext();
|
||||
_.each(v_contexts, function(x) {v_context.add(x);});
|
||||
if (_.detect(v_contexts, function(x) {return !!x.__ref;})) {
|
||||
var fields_values = this._build_eval_context();
|
||||
v_context.set_eval_context(fields_values);
|
||||
}
|
||||
// if there is a context on the node, overrides the model's context
|
||||
var ctx = f_context || v_context;
|
||||
var ctx = v_contexts.length > 0 ? v_context : f_context;
|
||||
return ctx;
|
||||
},
|
||||
build_domain: function() {
|
||||
var f_domain = this.field.domain || null;
|
||||
var v_domain = this.node.attrs.domain || [];
|
||||
if (!(v_domain instanceof Array) || true) { //TODO niv: remove || true
|
||||
var fields_values = this._build_view_fields_values();
|
||||
var fields_values = this._build_eval_context();
|
||||
v_domain = new openerp.web.CompoundDomain(v_domain).set_eval_context(fields_values);
|
||||
}
|
||||
// if there is a domain on the node, overrides the model's domain
|
||||
|
@ -1034,10 +1112,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
|
|||
});
|
||||
|
||||
openerp.web.form.FieldChar = openerp.web.form.Field.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldChar";
|
||||
},
|
||||
template: 'FieldChar',
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('input').change(this.on_ui_change);
|
||||
|
@ -1046,6 +1121,7 @@ openerp.web.form.FieldChar = openerp.web.form.Field.extend({
|
|||
this._super.apply(this, arguments);
|
||||
var show_value = openerp.web.format_value(value, this, '');
|
||||
this.$element.find('input').val(show_value);
|
||||
return show_value;
|
||||
},
|
||||
update_dom: function() {
|
||||
this._super.apply(this, arguments);
|
||||
|
@ -1070,10 +1146,7 @@ openerp.web.form.FieldChar = openerp.web.form.Field.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldEmail";
|
||||
},
|
||||
template: 'FieldEmail',
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('button').click(this.on_button_clicked);
|
||||
|
@ -1084,18 +1157,11 @@ openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
|
|||
} else {
|
||||
location.href = 'mailto:' + this.value;
|
||||
}
|
||||
},
|
||||
set_value: function(value) {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('a').attr('href', 'mailto:' + this.$element.find('input').val());
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldUrl";
|
||||
},
|
||||
template: 'FieldUrl',
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('button').click(this.on_button_clicked);
|
||||
|
@ -1120,16 +1186,13 @@ openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
|
|||
}
|
||||
});
|
||||
|
||||
openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldDate";
|
||||
this.jqueryui_object = 'datetimepicker';
|
||||
},
|
||||
openerp.web.DateTimeWidget = openerp.web.Widget.extend({
|
||||
template: "web.datetimepicker",
|
||||
jqueryui_object: 'datetimepicker',
|
||||
type_of_date: "datetime",
|
||||
start: function() {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('input').change(this.on_ui_change);
|
||||
this.$element.find('input').change(this.on_change);
|
||||
this.picker({
|
||||
onSelect: this.on_picker_select,
|
||||
changeMonth: true,
|
||||
|
@ -1147,6 +1210,8 @@ openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
|
|||
this.$element.find('button.oe_datepicker_close').click(function() {
|
||||
self.$element.find('.oe_datepicker').hide();
|
||||
});
|
||||
this.set_readonly(false);
|
||||
this.value = false;
|
||||
},
|
||||
picker: function() {
|
||||
return $.fn[this.jqueryui_object].apply(this.$element.find('.oe_datepicker_container'), arguments);
|
||||
|
@ -1156,68 +1221,98 @@ openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
|
|||
this.$element.find('input').val(date ? this.format_client(date) : '').change();
|
||||
},
|
||||
set_value: function(value) {
|
||||
value = this.parse(value);
|
||||
this._super(value);
|
||||
this.value = value;
|
||||
this.$element.find('input').val(value ? this.format_client(value) : '');
|
||||
},
|
||||
get_value: function() {
|
||||
return this.format(this.value);
|
||||
return this.value;
|
||||
},
|
||||
set_value_from_ui: function() {
|
||||
var value = this.$element.find('input').val() || false;
|
||||
this.value = this.parse_client(value);
|
||||
this._super();
|
||||
},
|
||||
update_dom: function() {
|
||||
this._super.apply(this, arguments);
|
||||
set_readonly: function(readonly) {
|
||||
this.readonly = readonly;
|
||||
this.$element.find('input').attr('disabled', this.readonly);
|
||||
this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', this.readonly);
|
||||
this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly);
|
||||
},
|
||||
validate: function() {
|
||||
this.invalid = false;
|
||||
is_valid: function(required) {
|
||||
var value = this.$element.find('input').val();
|
||||
if (value === "") {
|
||||
this.invalid = this.required;
|
||||
return !required;
|
||||
} else {
|
||||
try {
|
||||
this.parse_client(value);
|
||||
this.invalid = false;
|
||||
return true;
|
||||
} catch(e) {
|
||||
this.invalid = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
focus: function() {
|
||||
this.$element.find('input').focus();
|
||||
},
|
||||
parse: openerp.web.auto_str_to_date,
|
||||
parse_client: function(v) {
|
||||
return openerp.web.parse_value(v, this.field);
|
||||
},
|
||||
format: function(val) {
|
||||
return openerp.web.auto_date_to_str(val, this.field.type);
|
||||
return openerp.web.parse_value(v, {"widget": this.type_of_date});
|
||||
},
|
||||
format_client: function(v) {
|
||||
return openerp.web.format_value(v, this.field);
|
||||
return openerp.web.format_value(v, {"widget": this.type_of_date});
|
||||
},
|
||||
on_change: function() {
|
||||
if (this.is_valid()) {
|
||||
this.set_value_from_ui();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.jqueryui_object = 'datepicker';
|
||||
},
|
||||
openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
|
||||
jqueryui_object: 'datepicker',
|
||||
type_of_date: "date",
|
||||
on_picker_select: function(text, instance) {
|
||||
this._super(text, instance);
|
||||
this.$element.find('.oe_datepicker').hide();
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.form.FieldText = openerp.web.form.Field.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldText";
|
||||
openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
|
||||
template: "EmptyComponent",
|
||||
build_widget: function() {
|
||||
return new openerp.web.DateTimeWidget(this);
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
this.datewidget = this.build_widget();
|
||||
this.datewidget.on_change.add(this.on_ui_change);
|
||||
this.datewidget.appendTo(this.$element);
|
||||
},
|
||||
set_value: function(value) {
|
||||
this._super(value);
|
||||
this.datewidget.set_value(value);
|
||||
},
|
||||
get_value: function() {
|
||||
return this.datewidget.get_value();
|
||||
},
|
||||
update_dom: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.datewidget.set_readonly(this.readonly);
|
||||
},
|
||||
validate: function() {
|
||||
this.invalid = !this.datewidget.is_valid(this.required);
|
||||
},
|
||||
focus: function() {
|
||||
this.datewidget.focus();
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
|
||||
build_widget: function() {
|
||||
return new openerp.web.DateWidget(this);
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.form.FieldText = openerp.web.form.Field.extend({
|
||||
template: 'FieldText',
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('textarea').change(this.on_ui_change);
|
||||
|
@ -1250,10 +1345,7 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldBoolean";
|
||||
},
|
||||
template: 'FieldBoolean',
|
||||
start: function() {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
|
@ -1284,10 +1376,7 @@ openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldProgressBar";
|
||||
},
|
||||
template: 'FieldProgressBar',
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$element.find('div').progressbar({
|
||||
|
@ -1310,10 +1399,10 @@ openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
|
||||
template: 'FieldSelection',
|
||||
init: function(view, node) {
|
||||
var self = this;
|
||||
this._super(view, node);
|
||||
this.template = "FieldSelection";
|
||||
this.values = this.field.selection;
|
||||
_.each(this.values, function(v, i) {
|
||||
if (v[0] === false && v[1] === '') {
|
||||
|
@ -1420,9 +1509,9 @@ openerp.web.form.dialog = function(content, options) {
|
|||
};
|
||||
|
||||
openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
|
||||
template: 'FieldMany2One',
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldMany2One";
|
||||
this.limit = 7;
|
||||
this.value = null;
|
||||
this.cm_id = _.uniqueId('m2o_cm_');
|
||||
|
@ -1435,7 +1524,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
|
|||
this.$input = this.$element.find("input");
|
||||
this.$drop_down = this.$element.find(".oe-m2o-drop-down-button");
|
||||
this.$menu_btn = this.$element.find(".oe-m2o-cm-button");
|
||||
|
||||
|
||||
// context menu
|
||||
var init_context_menu_def = $.Deferred().then(function(e) {
|
||||
var rdataset = new openerp.web.DataSetStatic(self, "ir.values", self.build_context());
|
||||
|
@ -1443,7 +1532,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
|
|||
[[self.field.relation, false]], false, rdataset.get_context()], false, 0)
|
||||
.then(function(result) {
|
||||
self.related_entries = result;
|
||||
|
||||
|
||||
var $cmenu = $("#" + self.cm_id);
|
||||
$cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self}));
|
||||
var bindings = {};
|
||||
|
@ -1747,10 +1836,10 @@ var commands = {
|
|||
}
|
||||
};
|
||||
openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
|
||||
template: 'FieldOne2Many',
|
||||
multi_selection: false,
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldOne2Many";
|
||||
this.is_started = $.Deferred();
|
||||
this.form_last_update = $.Deferred();
|
||||
this.disable_utility_classes = true;
|
||||
|
@ -1781,6 +1870,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
|
|||
}
|
||||
if(view.view_type === "list") {
|
||||
view.options.selectable = self.multi_selection;
|
||||
} else if (view.view_type === "form") {
|
||||
view.options.not_interactible_on_create = true;
|
||||
}
|
||||
views.push(view);
|
||||
});
|
||||
|
@ -1840,6 +1931,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
|
|||
switch (command[0]) {
|
||||
case commands.CREATE:
|
||||
obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
|
||||
obj.defaults = {};
|
||||
self.dataset.to_create.push(obj);
|
||||
self.dataset.cache.push(_.clone(obj));
|
||||
ids.push(obj.id);
|
||||
|
@ -1869,6 +1961,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
|
|||
_.each(value, function(command) {
|
||||
var obj = {values: command};
|
||||
obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
|
||||
obj.defaults = {};
|
||||
self.dataset.to_create.push(obj);
|
||||
self.dataset.cache.push(_.clone(obj));
|
||||
ids.push(obj.id);
|
||||
|
@ -1913,11 +2006,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
|
|||
var view = this.viewmanager.views[this.viewmanager.active_view].controller;
|
||||
if (this.viewmanager.active_view === "form") {
|
||||
var res = $.when(view.do_save());
|
||||
if (res === false) {
|
||||
// ignore
|
||||
} else if (res.isRejected()) {
|
||||
throw "Save or create on one2many dataset is not supposed to fail.";
|
||||
} else if (!res.isResolved()) {
|
||||
if (!res.isResolved() && !res.isRejected()) {
|
||||
throw "Asynchronous get_value() is not supported in form view.";
|
||||
}
|
||||
return res;
|
||||
|
@ -1931,9 +2020,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
|
|||
},
|
||||
validate: function() {
|
||||
this.invalid = false;
|
||||
var self = this;
|
||||
var view = self.viewmanager.views[self.viewmanager.active_view].controller;
|
||||
if (self.viewmanager.active_view === "form") {
|
||||
var view = this.viewmanager.views[this.viewmanager.active_view].controller;
|
||||
if (this.viewmanager.active_view === "form") {
|
||||
for (var f in view.fields) {
|
||||
f = view.fields[f];
|
||||
if (!f.is_valid()) {
|
||||
|
@ -1970,6 +2058,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
|
|||
} else {
|
||||
var self = this;
|
||||
var pop = new openerp.web.form.SelectCreatePopup(this);
|
||||
pop.on_default_get.add(self.dataset.on_default_get);
|
||||
pop.select_element(self.o2m.field.relation,{
|
||||
initial_view: "form",
|
||||
alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
|
||||
|
@ -1979,7 +2068,8 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
|
|||
self.o2m.dataset.on_change();
|
||||
});
|
||||
},
|
||||
parent_view: self.o2m.view
|
||||
parent_view: self.o2m.view,
|
||||
form_view_options: {'not_interactible_on_create':true}
|
||||
}, self.o2m.build_domain(), self.o2m.build_context());
|
||||
pop.on_select_elements.add_last(function() {
|
||||
self.o2m.reload_current_view();
|
||||
|
@ -1995,7 +2085,8 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
|
|||
parent_view: self.o2m.view,
|
||||
read_function: function() {
|
||||
return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
|
||||
}
|
||||
},
|
||||
form_view_options: {'not_interactible_on_create':true}
|
||||
});
|
||||
pop.on_write.add(function(id, data) {
|
||||
self.o2m.dataset.write(id, data, {}, function(r) {
|
||||
|
@ -2006,10 +2097,10 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
|
||||
template: 'FieldMany2Many',
|
||||
multi_selection: false,
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldMany2Many";
|
||||
this.list_id = _.uniqueId("many2many");
|
||||
this.is_started = $.Deferred();
|
||||
},
|
||||
|
@ -2110,6 +2201,8 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
|
|||
* - alternative_form_view
|
||||
* - create_function (defaults to a naive saving behavior)
|
||||
* - parent_view
|
||||
* - form_view_options
|
||||
* - list_view_options
|
||||
*/
|
||||
select_element: function(model, options, domain, context) {
|
||||
var self = this;
|
||||
|
@ -2131,6 +2224,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
|
|||
this.dataset = new openerp.web.ReadOnlyDataSetSearch(this, this.model,
|
||||
this.context);
|
||||
this.dataset.parent_view = this.options.parent_view;
|
||||
this.dataset.on_default_get.add(this.on_default_get);
|
||||
if (this.options.initial_view == "search") {
|
||||
this.setup_search_view();
|
||||
} else { // "form"
|
||||
|
@ -2173,7 +2267,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
|
|||
});
|
||||
self.view_list = new openerp.web.form.SelectCreateListView(self,
|
||||
self.dataset, false,
|
||||
{'deletable': false});
|
||||
_.extend({'deletable': false}, self.options.list_view_options || {}));
|
||||
self.view_list.popup = self;
|
||||
self.view_list.appendTo($("#" + self.element_id + "_view_list")).pipe(function() {
|
||||
self.view_list.do_show();
|
||||
|
@ -2209,7 +2303,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
|
|||
this.view_list.$element.hide();
|
||||
}
|
||||
this.dataset.index = null;
|
||||
this.view_form = new openerp.web.FormView(this, this.dataset, false);
|
||||
this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options);
|
||||
if (this.options.alternative_form_view) {
|
||||
this.view_form.set_embedded_view(this.options.alternative_form_view);
|
||||
}
|
||||
|
@ -2253,7 +2347,8 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
|
|||
this.on_select_elements(this.created_elements);
|
||||
}
|
||||
this.stop();
|
||||
}
|
||||
},
|
||||
on_default_get: function(res) {}
|
||||
});
|
||||
|
||||
openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({
|
||||
|
@ -2283,6 +2378,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
|
|||
* - auto_write (default true)
|
||||
* - read_function
|
||||
* - parent_view
|
||||
* - form_view_options
|
||||
*/
|
||||
show_element: function(model, row_id, context, options) {
|
||||
this.model = model;
|
||||
|
@ -2318,7 +2414,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
|
|||
on_write_completed: function() {},
|
||||
setup_form_view: function() {
|
||||
var self = this;
|
||||
this.view_form = new openerp.web.FormView(this, this.dataset, false);
|
||||
this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options);
|
||||
if (this.options.alternative_form_view) {
|
||||
this.view_form.set_embedded_view(this.options.alternative_form_view);
|
||||
}
|
||||
|
@ -2351,9 +2447,9 @@ openerp.web.form.FormOpenDataset = openerp.web.ReadOnlyDataSetSearch.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.FieldReference = openerp.web.form.Field.extend({
|
||||
template: 'FieldReference',
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldReference";
|
||||
this.fields_view = {
|
||||
fields: {
|
||||
selection: {
|
||||
|
@ -2488,10 +2584,7 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldBinaryFile";
|
||||
},
|
||||
template: 'FieldBinaryFile',
|
||||
set_value: function(value) {
|
||||
this._super.apply(this, arguments);
|
||||
var show_value = (value != null && value !== false) ? value : '';
|
||||
|
@ -2519,10 +2612,7 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
|
|||
});
|
||||
|
||||
openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
this.template = "FieldBinaryImage";
|
||||
},
|
||||
template: 'FieldBinaryImage',
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.$image = this.$element.find('img.oe-binary-image');
|
||||
|
@ -2597,6 +2687,93 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
|
|||
}
|
||||
});
|
||||
|
||||
openerp.web.form.WidgetNotebookReadonly = openerp.web.form.WidgetNotebook.extend({
|
||||
template: 'WidgetNotebook.readonly'
|
||||
});
|
||||
openerp.web.form.FieldReadonly = openerp.web.form.Field.extend({
|
||||
|
||||
});
|
||||
openerp.web.form.FieldCharReadonly = openerp.web.form.FieldReadonly.extend({
|
||||
template: 'FieldChar.readonly',
|
||||
set_value: function (value) {
|
||||
this._super.apply(this, arguments);
|
||||
var show_value = openerp.web.format_value(value, this, '');
|
||||
this.$element.find('div').text(show_value);
|
||||
return show_value;
|
||||
}
|
||||
});
|
||||
openerp.web.form.FieldURIReadonly = openerp.web.form.FieldCharReadonly.extend({
|
||||
template: 'FieldURI.readonly',
|
||||
scheme: null,
|
||||
set_value: function (value) {
|
||||
var displayed = this._super.apply(this, arguments);
|
||||
this.$element.find('a')
|
||||
.attr('href', this.scheme + ':' + displayed)
|
||||
.text(displayed);
|
||||
}
|
||||
});
|
||||
openerp.web.form.FieldEmailReadonly = openerp.web.form.FieldURIReadonly.extend({
|
||||
scheme: 'mailto'
|
||||
});
|
||||
openerp.web.form.FieldUrlReadonly = openerp.web.form.FieldURIReadonly.extend({
|
||||
set_value: function (value) {
|
||||
var s = /(\w+):(\.+)/.match(value);
|
||||
if (!(s[0] === 'http' || s[0] === 'https')) { return; }
|
||||
this.scheme = s[0];
|
||||
this._super(s[1]);
|
||||
}
|
||||
});
|
||||
openerp.web.form.FieldBooleanReadonly = openerp.web.form.FieldCharReadonly.extend({
|
||||
set_value: function (value) {
|
||||
this._super(value ? '\u2714' : '\u2718');
|
||||
}
|
||||
});
|
||||
openerp.web.form.FieldSelectionReadonly = openerp.web.form.FieldReadonly.extend({
|
||||
template: 'FieldChar.readonly',
|
||||
init: function(view, node) {
|
||||
// lifted straight from r/w version
|
||||
var self = this;
|
||||
this._super(view, node);
|
||||
this.values = this.field.selection;
|
||||
_.each(this.values, function(v, i) {
|
||||
if (v[0] === false && v[1] === '') {
|
||||
self.values.splice(i, 1);
|
||||
}
|
||||
});
|
||||
this.values.unshift([false, '']);
|
||||
},
|
||||
set_value: function (value) {
|
||||
value = value === null ? false : value;
|
||||
value = value instanceof Array ? value[0] : value;
|
||||
var option = _(this.values)
|
||||
.detect(function (record) { return record[0] === value; });
|
||||
this._super(value);
|
||||
this.$element.find('div').text(option ? option[1] : this.values[0][1]);
|
||||
}
|
||||
});
|
||||
openerp.web.form.FieldMany2OneReadonly = openerp.web.form.FieldCharReadonly.extend({
|
||||
set_value: function (value) {
|
||||
value = value || null;
|
||||
this.invalid = false;
|
||||
var self = this;
|
||||
this.tmp_value = value;
|
||||
self.update_dom();
|
||||
self.on_value_changed();
|
||||
var real_set_value = function(rval) {
|
||||
self.$element.find('div').text(rval ? rval[1] : '');
|
||||
};
|
||||
if(typeof(value) === "number") {
|
||||
var dataset = new openerp.web.DataSetStatic(
|
||||
this, this.field.relation, self.build_context());
|
||||
dataset.name_get([value], function(data) {
|
||||
real_set_value(data[0]);
|
||||
}).fail(function() {self.tmp_value = undefined;});
|
||||
} else {
|
||||
setTimeout(function() {real_set_value(value);}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Registry of form widgets, called by :js:`openerp.web.FormView`
|
||||
*/
|
||||
|
@ -2629,6 +2806,22 @@ openerp.web.form.widgets = new openerp.web.Registry({
|
|||
'binary': 'openerp.web.form.FieldBinaryFile',
|
||||
'statusbar': 'openerp.web.form.FieldStatus'
|
||||
});
|
||||
openerp.web.form.readonly = openerp.web.form.widgets.clone({
|
||||
'notebook': 'openerp.web.form.WidgetNotebookReadonly',
|
||||
'char': 'openerp.web.form.FieldCharReadonly',
|
||||
'email': 'openerp.web.form.FieldEmailReadonly',
|
||||
'url': 'openerp.web.form.FieldUrlReadonly',
|
||||
'text': 'openerp.web.form.FieldCharReadonly',
|
||||
'text_wiki' : 'openerp.web.form.FieldCharReadonly',
|
||||
'date': 'openerp.web.form.FieldCharReadonly',
|
||||
'datetime': 'openerp.web.form.FieldCharReadonly',
|
||||
'selection' : 'openerp.web.form.FieldSelectionReadonly',
|
||||
'many2one': 'openerp.web.form.FieldMany2OneReadonly',
|
||||
'boolean': 'openerp.web.form.FieldBooleanReadonly',
|
||||
'float': 'openerp.web.form.FieldCharReadonly',
|
||||
'integer': 'openerp.web.form.FieldCharReadonly',
|
||||
'float_time': 'openerp.web.form.FieldCharReadonly'
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
|
|||
this.model = dataset.model;
|
||||
this.view_id = view_id;
|
||||
this.previous_colspan = null;
|
||||
this.colors = null;
|
||||
|
||||
this.columns = [];
|
||||
|
||||
|
@ -75,6 +76,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
|
|||
}
|
||||
self.compute_aggregates();
|
||||
});
|
||||
|
||||
},
|
||||
/**
|
||||
* Retrieves the view's number of records per page (|| section)
|
||||
|
@ -131,6 +133,31 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
|
|||
this.$element.addClass('oe-listview');
|
||||
return this.reload_view(null, null, true);
|
||||
},
|
||||
/**
|
||||
* Returns the color for the provided record in the current view (from the
|
||||
* ``@colors`` attribute)
|
||||
*
|
||||
* @param {Record} record record for the current row
|
||||
* @returns {String} CSS color declaration
|
||||
*/
|
||||
color_for: function (record) {
|
||||
if (!this.colors) { return ''; }
|
||||
var context = _.extend({}, record.attributes, {
|
||||
uid: this.session.uid,
|
||||
current_date: new Date().toString('yyyy-MM-dd')
|
||||
// TODO: time, datetime, relativedelta
|
||||
});
|
||||
for(var i=0, len=this.colors.length; i<len; ++i) {
|
||||
var pair = this.colors[i],
|
||||
color = pair[0],
|
||||
expression = pair[1];
|
||||
if (py.evaluate(expression, context)) {
|
||||
return 'color: ' + color + ';';
|
||||
}
|
||||
// TODO: handle evaluation errors
|
||||
}
|
||||
return '';
|
||||
},
|
||||
/**
|
||||
* Called after loading the list view's description, sets up such things
|
||||
* as the view table's columns, renders the table itself and hooks up the
|
||||
|
@ -159,6 +186,17 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
|
|||
this.fields_view = data;
|
||||
this.name = "" + this.fields_view.arch.attrs.string;
|
||||
|
||||
if (this.fields_view.arch.attrs.colors) {
|
||||
this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain()
|
||||
.compact()
|
||||
.map(function(color_pair) {
|
||||
var pair = color_pair.split(':'),
|
||||
color = pair[0],
|
||||
expr = pair[1];
|
||||
return [color, py.parse(py.tokenize(expr)), expr];
|
||||
}).value();
|
||||
}
|
||||
|
||||
this.setup_columns(this.fields_view.fields, grouped);
|
||||
|
||||
this.$element.html(QWeb.render("ListView", this));
|
||||
|
@ -415,8 +453,8 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
|
|||
*/
|
||||
do_search: function (domains, contexts, groupbys) {
|
||||
return this.rpc('/web/session/eval_domain_and_context', {
|
||||
domains: [this.dataset.get_domain()].concat(domains),
|
||||
contexts: [this.dataset.get_context()].concat(contexts),
|
||||
domains: _([this.dataset.get_domain()].concat(domains)).compact(),
|
||||
contexts: _([this.dataset.get_context()].concat(contexts)).compact(),
|
||||
group_by_seq: groupbys
|
||||
}, $.proxy(this, 'do_actual_search'));
|
||||
},
|
||||
|
@ -541,6 +579,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
|
|||
if (_.isEmpty(records)) {
|
||||
records = this.groups.get_records();
|
||||
}
|
||||
records = _(records).compact();
|
||||
|
||||
var count = 0, sums = {};
|
||||
_(columns).each(function (column) {
|
||||
|
@ -1235,6 +1274,9 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
|
|||
},
|
||||
get_records: function () {
|
||||
if (_(this.children).isEmpty()) {
|
||||
if (!this.datagroup.length) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
count: this.datagroup.length,
|
||||
values: this.datagroup.aggregates
|
||||
|
@ -1351,10 +1393,10 @@ var Record = openerp.web.Class.extend(/** @lends Record# */{
|
|||
* @returns {Object} record displayable in a form view
|
||||
*/
|
||||
toForm: function () {
|
||||
var form_data = {};
|
||||
_(this.attributes).each(function (value, key) {
|
||||
form_data[key] = {value: value};
|
||||
});
|
||||
var form_data = {}, attrs = this.attributes;
|
||||
for(var k in attrs) {
|
||||
form_data[k] = {value: attrs[k]};
|
||||
}
|
||||
|
||||
return {data: form_data};
|
||||
}
|
||||
|
|
|
@ -182,9 +182,11 @@ openerp.web.list_editable = function (openerp) {
|
|||
self.edition_id = record_id;
|
||||
self.edition_form = _.extend(new openerp.web.ListEditableFormView(self, self.dataset, false), {
|
||||
form_template: 'ListView.row.form',
|
||||
registry: openerp.web.list.form.widgets
|
||||
registry: openerp.web.list.form.widgets,
|
||||
$element: $new_row
|
||||
});
|
||||
self.edition_form.appendTo($("#" + $new_row.attr('id')));
|
||||
// HA HA
|
||||
self.edition_form.appendTo();
|
||||
$.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () {
|
||||
// put in $.when just in case FormView.on_loaded becomes asynchronous
|
||||
$new_row.find('td')
|
||||
|
@ -335,6 +337,9 @@ openerp.web.list_editable = function (openerp) {
|
|||
});
|
||||
|
||||
openerp.web.ListEditableFormView = openerp.web.FormView.extend({
|
||||
init_view: function() {}
|
||||
init_view: function() {},
|
||||
_render_and_insert: function () {
|
||||
return this.start();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -86,7 +86,7 @@ db.web.ActionManager = db.web.Widget.extend({
|
|||
console.log("Action manager can't handle action of type " + action.type, action);
|
||||
return;
|
||||
}
|
||||
this[type](action, on_close);
|
||||
return this[type](action, on_close);
|
||||
},
|
||||
ir_actions_act_window: function (action, on_close) {
|
||||
if (action.target === 'new') {
|
||||
|
@ -163,7 +163,7 @@ db.web.ViewManager = db.web.Widget.extend(/** @lends db.web.ViewManager# */{
|
|||
*/
|
||||
init: function(parent, dataset, views) {
|
||||
this._super(parent);
|
||||
this.model = dataset.model;
|
||||
this.model = dataset ? dataset.model : undefined;
|
||||
this.dataset = dataset;
|
||||
this.searchview = null;
|
||||
this.active_view = null;
|
||||
|
@ -184,7 +184,6 @@ db.web.ViewManager = db.web.Widget.extend(/** @lends db.web.ViewManager# */{
|
|||
start: function() {
|
||||
this._super();
|
||||
var self = this;
|
||||
this.dataset.start();
|
||||
this.$element.find('.oe_vm_switch button').click(function() {
|
||||
self.on_mode_switch($(this).data('view-type'));
|
||||
});
|
||||
|
@ -332,6 +331,7 @@ db.web.ViewManagerAction = db.web.ViewManager.extend(/** @lends oepnerp.web.View
|
|||
// dataset initialization will take the session from ``this``, so if we
|
||||
// do not have it yet (and we don't, because we've not called our own
|
||||
// ``_super()``) rpc requests will blow up.
|
||||
this._super(parent, null, action.views);
|
||||
this.session = parent.session;
|
||||
this.action = action;
|
||||
var dataset = new db.web.DataSetSearch(this, action.res_model, action.context, action.domain);
|
||||
|
@ -339,7 +339,7 @@ db.web.ViewManagerAction = db.web.ViewManager.extend(/** @lends oepnerp.web.View
|
|||
dataset.ids.push(action.res_id);
|
||||
dataset.index = 0;
|
||||
}
|
||||
this._super(parent, dataset, action.views);
|
||||
this.dataset = dataset;
|
||||
this.flags = this.action.flags || {};
|
||||
if (action.res_model == 'board.board' && action.views.length == 1 && action.views) {
|
||||
// Not elegant but allows to avoid form chrome (pager, save/new
|
||||
|
@ -470,7 +470,7 @@ db.web.ViewManagerAction = db.web.ViewManager.extend(/** @lends oepnerp.web.View
|
|||
* Intercept do_action resolution from children views
|
||||
*/
|
||||
on_action_executed: function () {
|
||||
new db.web.DataSet(this, 'res.log')
|
||||
return new db.web.DataSet(this, 'res.log')
|
||||
.call('get', [], this.do_display_log);
|
||||
},
|
||||
/**
|
||||
|
@ -770,28 +770,31 @@ db.web.View = db.web.Widget.extend(/** @lends db.web.View# */{
|
|||
var self = this;
|
||||
var result_handler = function () {
|
||||
if (on_closed) { on_closed.apply(null, arguments); }
|
||||
self.widget_parent.on_action_executed.apply(null, arguments);
|
||||
return self.widget_parent.on_action_executed.apply(null, arguments);
|
||||
};
|
||||
var handler = function (r) {
|
||||
var action = r.result;
|
||||
if (action && action.constructor == Object) {
|
||||
action.context = action.context || {};
|
||||
_.extend(action.context, {
|
||||
active_id: record_id || false,
|
||||
active_ids: [record_id || false],
|
||||
active_model: dataset.model
|
||||
return self.rpc('/web/session/eval_domain_and_context', {
|
||||
contexts: [dataset.get_context(), action.context || {}, {
|
||||
active_id: record_id || false,
|
||||
active_ids: [record_id || false],
|
||||
active_model: dataset.model
|
||||
}],
|
||||
domains: []
|
||||
}).pipe(function (results) {
|
||||
action.context = results.context
|
||||
return self.do_action(action, result_handler);
|
||||
});
|
||||
action.context = new db.web.CompoundContext(dataset.get_context(), action.context);
|
||||
self.do_action(action, result_handler);
|
||||
} else {
|
||||
result_handler();
|
||||
return result_handler();
|
||||
}
|
||||
};
|
||||
|
||||
var context = new db.web.CompoundContext(dataset.get_context(), action_data.context || {});
|
||||
|
||||
if (action_data.special) {
|
||||
handler({result: {"type":"ir.actions.act_window_close"}});
|
||||
return handler({result: {"type":"ir.actions.act_window_close"}});
|
||||
} else if (action_data.type=="object") {
|
||||
return dataset.call_button(action_data.name, [[record_id], context], handler);
|
||||
} else if (action_data.type=="action") {
|
||||
|
@ -863,6 +866,8 @@ db.web.View = db.web.Widget.extend(/** @lends db.web.View# */{
|
|||
console.log('Todo');
|
||||
},
|
||||
on_sidebar_import: function() {
|
||||
var import_view = new db.web.DataImport(this, this.dataset);
|
||||
import_view.start();
|
||||
},
|
||||
on_sidebar_export: function() {
|
||||
var export_view = new db.web.DataExport(this, this.dataset);
|
||||
|
|
|
@ -38,11 +38,17 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" id="oe_secondary_menu" class="secondary_menu">
|
||||
</td>
|
||||
<td valign="top" class="oe-application-container">
|
||||
<div id="oe_app" class="oe-application">
|
||||
</div>
|
||||
<td colspan="2" valign="top" height="100%">
|
||||
<table cellspacing="0" cellpadding="0" border="0" height="100%" width="100%">
|
||||
<tr>
|
||||
<td valign="top" id="oe_secondary_menu" class="secondary_menu">
|
||||
</td>
|
||||
<td valign="top" class="oe-application-container">
|
||||
<div id="oe_app" class="oe-application">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -236,7 +242,23 @@
|
|||
</table>
|
||||
</form>
|
||||
</t>
|
||||
|
||||
<t t-name="Login_dblist">
|
||||
<select name="db">
|
||||
<t t-foreach="db_list" t-as="db">
|
||||
<t t-if="selected_db === db">
|
||||
<option t-att-value="db" selected="true">
|
||||
<t t-esc="db"/></option>
|
||||
</t>
|
||||
<t t-if="selected_db !== db">
|
||||
<option t-att-value="db"><t t-esc="db"/></option>
|
||||
</t>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
|
||||
<t t-name="Login">
|
||||
<div>
|
||||
<form class="oe_forms">
|
||||
<fieldset>
|
||||
<legend style="">
|
||||
|
@ -247,33 +269,18 @@
|
|||
<tr>
|
||||
<td><label for="db">Database:</label></td>
|
||||
<td>
|
||||
<t t-if="!db_list">
|
||||
<input type="text" name="db" t-att-value="selected_db || ''" autofocus="true"/>
|
||||
</t>
|
||||
<t t-if="db_list">
|
||||
<select name="db">
|
||||
<t t-foreach="db_list" t-as="db">
|
||||
<t t-if="selected_db === db">
|
||||
<option t-att-value="db" selected="true">
|
||||
<t t-esc="db"/></option>
|
||||
</t>
|
||||
<t t-if="selected_db !== db">
|
||||
<option t-att-value="db"><t t-esc="db"/></option>
|
||||
</t>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
<input type="text" name="db" t-att-value="widget.selected_db || ''" autofocus="true"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="login">User:</label></td>
|
||||
<td><input type="text" name="login"
|
||||
t-att-value="selected_login || ''" autofocus="true"/></td>
|
||||
t-att-value="widget.selected_login || ''" autofocus="true"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="password">Password:</label></td>
|
||||
<td><input type="password" name="password"
|
||||
t-att-value="selected_password || ''"/></td>
|
||||
t-att-value="widget.selected_password || ''"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
|
@ -324,6 +331,7 @@
|
|||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="Header">
|
||||
<div>
|
||||
|
@ -342,22 +350,12 @@
|
|||
<li>
|
||||
<a t-att-href="'/' + widget.qs" title="Home" class="home"><img src="/web/static/src/img/header-home.png" width="16" height="16" border="0"/></a>
|
||||
</li>
|
||||
<!--
|
||||
<li>
|
||||
<a href="#requests" title="Requests" class="requests"><img src="/web/static/src/img/header-requests.png" width="16" height="16" border="0"/><small>1</small></a>
|
||||
</li>
|
||||
-->
|
||||
<li class="preferences">
|
||||
<a href="#preferences" title="Preferences" class="preferences"><img src="/web/static/src/img/header-preferences.png" width="16" height="16" border="0"/></a>
|
||||
<a href="javascript:void(0)" title="Preferences" class="preferences"><img src="/web/static/src/img/header-preferences.png" width="16" height="16" border="0"/></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#about" title="About" class="about"><img src="/web/static/src/img/header-about.png" width="16" height="16" border="0"/></a>
|
||||
<a href="javascript:void(0)" title="About" class="about"><img src="/web/static/src/img/header-about.png" width="16" height="16" border="0"/></a>
|
||||
</li>
|
||||
<!--
|
||||
<li>
|
||||
<a href="http://doc.openerp.com/v6.0/book?version=$version" title="Help" target="_blank" class="help"><img src="/web/static/src/img/header-help.png" width="16" height="16" border="0"/></a>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
<div class="block">
|
||||
<a href="#logout" class="logout">LOGOUT</a>
|
||||
|
@ -374,45 +372,39 @@
|
|||
</ul>
|
||||
<t t-name="Menu">
|
||||
<table align="center">
|
||||
<tr>
|
||||
<t t-foreach="data.children" t-as="menu">
|
||||
<td>
|
||||
<a href="#" t-att-data-menu="menu.id">
|
||||
<t t-esc="menu.name"/>
|
||||
</a>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
<tr>
|
||||
<td t-foreach="widget.data.data.children" t-as="menu">
|
||||
<a href="#" t-att-data-menu="menu.id">
|
||||
<t t-esc="menu.name"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
<t t-name="Menu.secondary">
|
||||
<div style="display: none" class="menu_accordion" t-att-data-menu-parent="menu.id">
|
||||
<div t-attf-class="oe_toggle_secondary_menu">
|
||||
<span class="oe_menu_fold" title="Fold menu">&laquo;</span>
|
||||
<span class="oe_menu_unfold" title="Unfold menu">&raquo;</span>
|
||||
</div>
|
||||
<div t-foreach="widget.data.data.children" t-as="menu" style="display: none" class="oe_secondary_menu" t-att-data-menu-parent="menu.id">
|
||||
<t t-foreach="menu.children" t-as="menu">
|
||||
<t t-set="header">h3</t>
|
||||
<t t-set="classname">oe_secondary_menu_item</t>
|
||||
<t t-set="level" t-value="0"/>
|
||||
<t t-call="Menu.secondary.children"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="Menu.secondary.children">
|
||||
<<t t-esc="header"/>>
|
||||
<a href="#" t-attf-id="menu_#{menu.id}">
|
||||
<span><t t-esc="menu.name"/></span>
|
||||
</a>
|
||||
</<t t-esc="header"/>>
|
||||
<div class="menu_content">
|
||||
<t t-set="level" t-value="level + 1"/>
|
||||
<a href="#" t-att-id="menu.children.length ? 'menu_' + menu.id : undefined"
|
||||
t-att-class="classname + (menu.children.length ? ' submenu' : ' leaf') + (menu_first and level == 1 ? ' opened' : '')"
|
||||
t-att-data-menu="menu.children.length ? undefined : menu.id">
|
||||
<span t-attf-style="padding-left: #{(level - 2) * 20}px"> <t t-esc="menu.name"/></span>
|
||||
</a>
|
||||
<div t-attf-class="oe_secondary_submenu" t-if="menu.children.length" t-att-style="menu_first and level == 1 ? undefined : 'display: none'">
|
||||
<t t-foreach="menu.children" t-as="menu">
|
||||
<t t-if="!menu.children.length">
|
||||
<a href="#" class="leaf" t-att-data-menu="menu.id">
|
||||
<span><t t-esc="menu.name"/></span>
|
||||
</a>
|
||||
</t>
|
||||
<t t-if="menu.children.length">
|
||||
<div class="submenu_accordion">
|
||||
<t t-call="Menu.secondary.children">
|
||||
<t t-set="header">h4</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-set="classname">oe_secondary_submenu_item</t>
|
||||
<t t-call="Menu.secondary.children"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
@ -624,7 +616,8 @@
|
|||
</t>
|
||||
</t>
|
||||
<tr t-name="ListView.row" t-att-class="row_parity"
|
||||
t-att-data-id="record.get('id')">
|
||||
t-att-data-id="record.get('id')"
|
||||
t-att-style="view.color_for(record)">
|
||||
<t t-foreach="columns" t-as="column">
|
||||
<td t-if="column.meta">
|
||||
|
||||
|
@ -658,7 +651,7 @@
|
|||
<t t-raw="frame.render()"/>
|
||||
</t>
|
||||
<t t-name="FormView">
|
||||
<div class="oe_form_header" t-att-id="view.element_id + '_header'">
|
||||
<div class="oe_form_header">
|
||||
<div class="oe_form_buttons" t-if="view.options.action_buttons !== false">
|
||||
<!--<button type="button" class="oe_form_button_save">
|
||||
<span class="oe_form_on_update">Save</span>
|
||||
|
@ -671,6 +664,7 @@
|
|||
<!--<button type="button" class="oe_form_button_cancel">Cancel</button>-->
|
||||
<button type="button" class="oe_form_button_new">New</button>
|
||||
<button type="button" class="oe_form_button_duplicate oe_form_on_update">Duplicate</button>
|
||||
<button type="button" class="oe_form_button_toggle">Readonly/Editable</button>
|
||||
</div>
|
||||
<div class="oe_form_pager" t-if="view.options.pager !== false">
|
||||
<button type="button" data-pager-action="first">First</button>
|
||||
|
@ -730,8 +724,7 @@
|
|||
t-att-width="td.width"
|
||||
t-att-nowrap="td.nowrap or td.is_field_m2o? 'true' : undefined"
|
||||
t-att-valign="td.table ? 'top' : undefined"
|
||||
t-att-id="td.element_id"
|
||||
t-attf-class="oe_form_frame_cell #{td.classname}"
|
||||
t-attf-class="oe_form_frame_cell #{td.classname} #{td.element_class}"
|
||||
>
|
||||
<t t-raw="td.render()"/>
|
||||
</td>
|
||||
|
@ -741,8 +734,8 @@
|
|||
</t>
|
||||
<t t-name="WidgetNotebook">
|
||||
<ul>
|
||||
<li t-foreach="widget.pages" t-as="page" t-att-id="page.element_tab_id">
|
||||
<a t-att-href="'#' + page.element_id">
|
||||
<li t-foreach="widget.pages" t-as="page">
|
||||
<a href="#">
|
||||
<t t-esc="page.string"/>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -752,17 +745,23 @@
|
|||
</t>
|
||||
</t>
|
||||
<t t-name="WidgetNotebookPage">
|
||||
<div t-att-id="widget.element_id">
|
||||
<div>
|
||||
<t t-call="WidgetFrame"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="WidgetNotebook.readonly">
|
||||
<t t-foreach="widget.pages" t-as="page">
|
||||
<h3><t t-esc="page.string"/></h3>
|
||||
<t t-raw="page.render()"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="WidgetSeparator">
|
||||
<div t-if="widget.orientation !== 'vertical'" t-att-class="'separator ' + widget.orientation">
|
||||
<t t-esc="widget.string"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="WidgetLabel">
|
||||
<label t-att-for="widget.element_id + '_field'"
|
||||
<label t-att-for="widget.element_id"
|
||||
t-att-class="'oe_label' + (widget.help ? '_help' : '')"
|
||||
t-att-title="widget.help">
|
||||
<t t-esc="widget.string"/>
|
||||
|
@ -776,12 +775,22 @@
|
|||
<t t-name="FieldChar">
|
||||
<input type="text" size="1"
|
||||
t-att-name="widget.name"
|
||||
t-att-id="widget.element_id + '_field'"
|
||||
t-att-class="'field_' + widget.type"
|
||||
t-att-id="widget.element_id"
|
||||
t-attf-class="field_#{widget.type} #{widget.element_class}"
|
||||
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%"
|
||||
/>
|
||||
<img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/>
|
||||
</t>
|
||||
<t t-name="FieldChar.readonly">
|
||||
<div
|
||||
t-att-id="widget.element_id"
|
||||
t-attf-class="field_#{widget.type} #{widget.element_class}"
|
||||
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%">
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="FieldURI.readonly">
|
||||
<a href="#">#</a>
|
||||
</t>
|
||||
<t t-name="FieldEmail">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tr>
|
||||
|
@ -813,26 +822,28 @@
|
|||
<t t-name="FieldText">
|
||||
<textarea rows="6"
|
||||
t-att-name="widget.name"
|
||||
t-att-id="widget.element_id + '_field'"
|
||||
t-att-class="'field_' + widget.type"
|
||||
t-att-id="widget.element_id"
|
||||
t-attf-class="field_#{widget.type} #{widget.element_class}"
|
||||
t-attf-style="width: #{widget.field.translate ? '99' : '100'}%"
|
||||
></textarea>
|
||||
<img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/>
|
||||
</t>
|
||||
<t t-name="FieldDate">
|
||||
<t t-call="FieldChar"/>
|
||||
<img class="oe_input_icon oe_datepicker_trigger" src="/web/static/src/img/ui/field_calendar.png"
|
||||
title="Select date" width="16" height="16" border="0"/>
|
||||
<div class="oe_datepicker ui-widget-content ui-corner-all" style="display: none; position: absolute; z-index: 1;">
|
||||
<div class="oe_datepicker_container"/>
|
||||
<button type="button" class="oe_datepicker_close ui-state-default ui-priority-primary ui-corner-all" style="float: right;">Done</button>
|
||||
<t t-name="web.datetimepicker">
|
||||
<div class="oe_datepicker_root">
|
||||
<input type="text" size="1" style="width: 100%"/>
|
||||
<img class="oe_input_icon oe_datepicker_trigger" src="/web/static/src/img/ui/field_calendar.png"
|
||||
title="Select date" width="16" height="16" border="0"/>
|
||||
<div class="oe_datepicker ui-widget-content ui-corner-all" style="display: none; position: absolute; z-index: 1;">
|
||||
<div class="oe_datepicker_container"/>
|
||||
<button type="button" class="oe_datepicker_close ui-state-default ui-priority-primary ui-corner-all" style="float: right;">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="FieldSelection">
|
||||
<select
|
||||
t-att-name="widget.name"
|
||||
t-att-id="widget.element_id + '_field'"
|
||||
t-att-class="'field_' + widget.type"
|
||||
t-attf-class="field_#{widget.type} #{widget.element_class}"
|
||||
style="width: 100%">
|
||||
<t t-foreach="widget.values" t-as="option">
|
||||
<option><t t-esc="option[1]"/></option>
|
||||
|
@ -840,9 +851,9 @@
|
|||
</select>
|
||||
</t>
|
||||
<t t-name="FieldMany2One">
|
||||
<div t-att-id="widget.element_id" class="oe-m2o">
|
||||
<input t-att-id="widget.element_id + '_input'" type="text" size="1" style="width: 100%;"/>
|
||||
<span class="oe-m2o-drop-down-button" t-att-id="widget.element_id + '_drop_down'">
|
||||
<div t-att-class="widget.element_class" class="oe-m2o">
|
||||
<input type="text" size="1" style="width: 100%;"/>
|
||||
<span class="oe-m2o-drop-down-button">
|
||||
<img src="/web/static/src/img/down-arrow.png" /></span>
|
||||
<span class="oe-m2o-cm-button" t-att-id="widget.name + '_open'">
|
||||
<img src="/web/static/src/img/icons/gtk-index.png"/></span>
|
||||
|
@ -865,8 +876,6 @@
|
|||
</ul>
|
||||
</t>
|
||||
<t t-name="FieldOne2Many">
|
||||
<div t-att-id="widget.element_id">
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="FieldMany2Many">
|
||||
<div t-att-id="widget.list_id"></div>
|
||||
|
@ -874,10 +883,10 @@
|
|||
<t t-name="FieldReference">
|
||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="oe_frame oe_forms">
|
||||
<tr>
|
||||
<td t-att-id="widget.selection.element_id" class="oe_form_frame_cell oe_form_selection">
|
||||
<td t-attf-class="oe_form_frame_cell oe_form_selection #{widget.selection.element_class}">
|
||||
<t t-raw="widget.selection.render()"/>
|
||||
</td>
|
||||
<td t-att-id="widget.m2o.element_id" class="oe_form_frame_cell oe_form_many2one" nowrap="true">
|
||||
<td class="oe_form_frame_cell oe_form_many2one #{widget.selection.element_class}" nowrap="true">
|
||||
<t t-raw="widget.m2o.render()"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -887,7 +896,7 @@
|
|||
<input type="checkbox"
|
||||
t-att-name="widget.name"
|
||||
t-att-id="widget.element_id + '_field'"
|
||||
t-att-class="'field_' + widget.type"/>
|
||||
t-attf-class="field_#{widget.type} #{widget.element_class}"/>
|
||||
</t>
|
||||
<t t-name="FieldProgressBar">
|
||||
<div t-opentag="true" class="oe-progressbar">
|
||||
|
@ -902,7 +911,7 @@
|
|||
t-att-border="widget.readonly ? 0 : 1"
|
||||
t-att-id="widget.element_id + '_field'"
|
||||
t-att-name="widget.name"
|
||||
t-att-class="'field_' + widget.type"
|
||||
t-attf-class="field_#{widget.type} #{widget.element_class}"
|
||||
t-att-width="widget.node.attrs.img_width || widget.node.attrs.width"
|
||||
t-att-height="widget.node.attrs.img_height || widget.node.attrs.height"
|
||||
/>
|
||||
|
@ -950,7 +959,7 @@
|
|||
<input type="text" size="1"
|
||||
t-att-name="widget.name"
|
||||
t-att-id="widget.element_id + '_field'"
|
||||
t-att-class="'field_' + widget.type" style="width: 100%"
|
||||
t-attf-class="field_#{widget.type} #{widget.element_class}" style="width: 100%"
|
||||
/>
|
||||
</td>
|
||||
<td class="oe-binary" nowrap="true">
|
||||
|
@ -995,7 +1004,7 @@
|
|||
</t>
|
||||
<t t-name="WidgetButton">
|
||||
<button type="button"
|
||||
t-att-id="widget.element_id + '_button'"
|
||||
t-attf-class="#{widget.element_class}"
|
||||
t-att-title="widget.help"
|
||||
style="width: 100%" class="button">
|
||||
<img t-if="widget.node.attrs.icon" t-att-src="'/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/>
|
||||
|
@ -1070,6 +1079,18 @@
|
|||
<t t-if="filters.length" t-raw="filters.render(defaults)"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="SearchView.date">
|
||||
<label t-att-class="'oe_label' + (attrs.help ? '_help' : '')"
|
||||
t-att-title="attrs.help"
|
||||
t-att-for="element_id">
|
||||
<t t-esc="attrs.string || attrs.name"/>
|
||||
<span t-if="attrs.help">?</span>
|
||||
</label>
|
||||
<div style="white-space: nowrap;">
|
||||
<span t-att-id="element_id"></span>
|
||||
<t t-if="filters.length" t-raw="filters.render(defaults)"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="SearchView.field.selection">
|
||||
<label t-att-title="attrs.help"
|
||||
t-att-class="'oe_label' + (attrs.help ? '_help' : '')"
|
||||
|
@ -1161,11 +1182,8 @@
|
|||
<t t-name="SearchView.extended_search.proposition.char">
|
||||
<input t-att-id="element_id" class="field_char"/>
|
||||
</t>
|
||||
<t t-name="SearchView.extended_search.proposition.datetime">
|
||||
<input t-att-id="element_id" class="field_datetime"/>
|
||||
</t>
|
||||
<t t-name="SearchView.extended_search.proposition.date">
|
||||
<input t-att-id="element_id" class="field_date"/>
|
||||
<t t-name="SearchView.extended_search.proposition.empty">
|
||||
<span t-att-id="element_id"></span>
|
||||
</t>
|
||||
<t t-name="SearchView.extended_search.proposition.integer">
|
||||
<input type="number" t-att-id="element_id" class="field_integer" step="1"/>
|
||||
|
@ -1437,6 +1455,79 @@
|
|||
</table>
|
||||
</form>
|
||||
</t>
|
||||
|
||||
<t t-name="ImportView">
|
||||
<a id="importview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Import</a>
|
||||
</t>
|
||||
<t t-name="ImportDataView">
|
||||
<form name="import_data" id="import_data" action="" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="session_id" t-att-value="session.session_id"/>
|
||||
<h2 class="separator horizontal">1. Import a .CSV file</h2>
|
||||
<p>Select a .CSV file to import. If you need a sample of file to import,
|
||||
you should use the export tool with the "Import Compatible" option.
|
||||
</p>
|
||||
<p>
|
||||
<label for="csvfile">CSV File:</label>
|
||||
<input type="file" id="csvfile" size="50" name="csvfile"/>
|
||||
</p>
|
||||
<h2 class="separator horizontal">2. Check your file format</h2>
|
||||
<div id="result"></div>
|
||||
<fieldset>
|
||||
<legend style="cursor:pointer;">Import Options</legend>
|
||||
<table style="display:none">
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<label for="file_has_headers">Does your file have titles?</label>
|
||||
<input type="checkbox" checked="checked"
|
||||
id="file_has_headers"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="csv_separator">Separator:</label></td>
|
||||
<td><input type="text" name="csvsep" id="csv_separator" value=","/></td>
|
||||
<td><label for="csv_delimiter">Delimiter:</label></td>
|
||||
<td><input type="text" name="csvdel" id="csv_delimiter" value='"'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="csv_encoding">Encoding:</label></td>
|
||||
<td>
|
||||
<select name="csvcode" id="csv_encoding">
|
||||
<option value="utf-8">UTF-8</option>
|
||||
<option value="latin1">Latin 1</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><label for="csv_skip" title="For use if CSV files have titles on multiple lines, skips more than a single line during import">
|
||||
Lines to skip<sup>?</sup>:</label></td>
|
||||
<td><input type="number" id="csv_skip" value="0" min="0"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</form>
|
||||
</t>
|
||||
<table t-name="ImportView.result"
|
||||
class="oe_import_grid" width="100%" style="margin: 5px 0;">
|
||||
<tr t-if="headers" class="oe_import_grid-header">
|
||||
<td t-foreach="headers" t-as="header" class="oe_import_grid-cell">
|
||||
<t t-esc="header"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td t-foreach="records[0]" t-as="column">
|
||||
<input class="sel_fields"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-foreach="records" t-as="record" class="oe_import_grid-row">
|
||||
<td t-foreach="record" t-as="cell" class="oe_import_grid-cell">
|
||||
<t t-esc="cell"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<t t-name="ImportView.error">
|
||||
<p style="white-space:pre-line;">The import failed due to:<t t-esc="error.message"/></p>
|
||||
<t t-if="error.preview">
|
||||
<p>Here is a preview of the file we could not import:</p>
|
||||
<pre><t t-esc="error.preview"/></pre>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="About-Page">
|
||||
<div>
|
||||
<h1>OpenERP Web</h1>
|
||||
|
|
|
@ -35,6 +35,7 @@ openerp.web_calendar.CalendarView = openerp.web.View.extend({
|
|||
},
|
||||
stop: function() {
|
||||
scheduler.clearAll();
|
||||
this._super();
|
||||
},
|
||||
on_loaded: function(data) {
|
||||
this.fields_view = data;
|
||||
|
|
|
@ -301,7 +301,7 @@ openerp.web_dashboard.ConfigOverview = openerp.web.View.extend({
|
|||
.then(this.on_records_loaded);
|
||||
},
|
||||
on_records_loaded: function (read_response, progress_response) {
|
||||
var records = read_response[0].records,
|
||||
var records = read_response,
|
||||
progress = progress_response[0];
|
||||
|
||||
var grouped_todos = _(records).chain()
|
||||
|
|
|
@ -94,8 +94,10 @@ openerp.web_default_home = function (openerp) {
|
|||
})
|
||||
},
|
||||
install_module: function (module_name) {
|
||||
var self = this;
|
||||
var Modules = new openerp.web.DataSetSearch(
|
||||
this, 'ir.module.module', null, [['name', '=', module_name], ['state', '=', 'uninstalled']]);
|
||||
this, 'ir.module.module', null,
|
||||
[['name', '=', module_name], ['state', '=', 'uninstalled']]);
|
||||
var Upgrade = new openerp.web.DataSet(this, 'base.module.upgrade');
|
||||
|
||||
$.blockUI({message:'<img src="/web/static/src/img/throbber2.gif">'});
|
||||
|
@ -105,13 +107,21 @@ openerp.web_default_home = function (openerp) {
|
|||
[_.pluck(records, 'id'), 'to install', ['uninstalled']],
|
||||
function () {
|
||||
Upgrade.call('upgrade_module', [[]], function () {
|
||||
$.unblockUI();
|
||||
// TODO: less brutal reloading
|
||||
window.location.reload(true);
|
||||
self.run_configuration_wizards();
|
||||
});
|
||||
}
|
||||
)
|
||||
});
|
||||
},
|
||||
run_configuration_wizards: function () {
|
||||
var self = this;
|
||||
new openerp.web.DataSet(this, 'res.config').call('start', [[]], function (action) {
|
||||
$.unblockUI();
|
||||
self.do_action(action, function () {
|
||||
// TODO: less brutal reloading
|
||||
window.location.reload(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
import controllers
|
|
@ -3,7 +3,7 @@
|
|||
"version": "2.0",
|
||||
"depends": ['web'],
|
||||
"js": [
|
||||
"static/lib/dhtmlxGraph/codebase/dhtmlxchart.js",
|
||||
"static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.js",
|
||||
"static/src/js/graph.js"],
|
||||
"css": ["static/lib/dhtmlxGraph/codebase/dhtmlxchart.css"],
|
||||
"active": True
|
||||
|
|
|
@ -19,11 +19,14 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
|
|||
|
||||
init: function(parent, dataset, view_id) {
|
||||
this._super(parent);
|
||||
this.view_manager = parent;
|
||||
this.dataset = dataset;
|
||||
this.dataset_index = 0;
|
||||
this.model = this.dataset.model;
|
||||
this.view_id = view_id;
|
||||
|
||||
this.first_field = null;
|
||||
this.abscissa = null;
|
||||
this.ordinate = null;
|
||||
this.columns = [];
|
||||
this.group_field = null;
|
||||
},
|
||||
do_show: function () {
|
||||
// TODO: re-trigger search
|
||||
|
@ -33,348 +36,265 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
|
|||
this.$element.hide();
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
this._super();
|
||||
return this.rpc("/web_graph/graphview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
|
||||
return $.when(
|
||||
this.dataset.call('fields_get', []),
|
||||
this.rpc('/web/view/load', {
|
||||
model: this.dataset.model,
|
||||
view_id: this.view_id,
|
||||
view_type: 'graph'
|
||||
})).then(function (fields_result, view_result) {
|
||||
self.fields = fields_result[0];
|
||||
self.fields_view = view_result[0];
|
||||
self.on_loaded();
|
||||
});
|
||||
},
|
||||
on_loaded: function(data) {
|
||||
this.all_fields = data.all_fields;
|
||||
this.fields_view = data.fields_view;
|
||||
this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
|
||||
this.view_id = this.fields_view.view_id;
|
||||
/**
|
||||
* Returns all object fields involved in the graph view
|
||||
*/
|
||||
list_fields: function () {
|
||||
var fs = [this.abscissa];
|
||||
fs.push.apply(fs, _(this.columns).pluck('name'));
|
||||
if (this.group_field) {
|
||||
fs.push(this.group_field);
|
||||
}
|
||||
return fs;
|
||||
},
|
||||
on_loaded: function() {
|
||||
this.chart = this.fields_view.arch.attrs.type || 'pie';
|
||||
this.fields = this.fields_view.fields;
|
||||
this.chart_info_fields = [];
|
||||
this.operator_field = '';
|
||||
this.operator_field_one = '';
|
||||
this.operator = [];
|
||||
this.group_field = [];
|
||||
this.orientation = this.fields_view.arch.attrs.orientation || '';
|
||||
this.elem_id = this.$element[0]['id'];
|
||||
this.orientation = this.fields_view.arch.attrs.orientation || 'vertical';
|
||||
|
||||
_.each(this.fields_view.arch.children, function (field) {
|
||||
if (field.attrs.operator) {
|
||||
this.operator.push(field.attrs.name);
|
||||
}
|
||||
else if (field.attrs.group) {
|
||||
this.group_field.push(field.attrs.name);
|
||||
}
|
||||
else {
|
||||
this.chart_info_fields.push(field.attrs.name);
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.operator_field = this.operator[0];
|
||||
if(this.operator.length > 1){
|
||||
this.operator_field_one = this.operator[1];
|
||||
}
|
||||
if(this.operator == ''){
|
||||
this.operator_field = this.chart_info_fields[1];
|
||||
}
|
||||
this.chart_info = this.chart_info_fields[0];
|
||||
this.x_title = this.fields[this.chart_info_fields[0]]['string'];
|
||||
this.y_title = this.fields[this.operator_field]['string'];
|
||||
this.load_chart();
|
||||
},
|
||||
|
||||
load_chart: function(data) {
|
||||
var self = this;
|
||||
var domain = false;
|
||||
if(data){
|
||||
this.x_title = this.all_fields[this.chart_info_fields]['string'];
|
||||
this.y_title = this.all_fields[this.operator_field]['string'];
|
||||
self.schedule_chart(data);
|
||||
}else{
|
||||
if(! _.isEmpty(this.view_manager.dataset.domain)){
|
||||
domain = this.view_manager.dataset.domain;
|
||||
}else if(! _.isEmpty(this.view_manager.action.domain)){
|
||||
domain = this.view_manager.action.domain;
|
||||
}
|
||||
this.dataset.domain = domain;
|
||||
this.dataset.context = this.view_manager.dataset.context;
|
||||
this.dataset.read_slice(_(this.fields).keys(),{}, function(res) {
|
||||
self.schedule_chart(res);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
schedule_chart: function(results) {
|
||||
this.$element.html(QWeb.render("GraphView", {"fields_view": this.fields_view, "chart": this.chart,'elem_id': this.elem_id}));
|
||||
|
||||
_.each(results, function (result) {
|
||||
_.each(result, function (field_value, field_name) {
|
||||
if (typeof field_value == 'object') {
|
||||
result[field_name] = field_value[field_value.length - 1];
|
||||
}
|
||||
if (typeof field_value == 'string') {
|
||||
var choices = this.all_fields[field_name]['selection'];
|
||||
_.each(choices, function (choice) {
|
||||
if (field_value == choice[0]) {
|
||||
result[field_name] = choice;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
var graph_data = {};
|
||||
_.each(results, function (result) {
|
||||
var group_key = [];
|
||||
if(this.group_field.length){
|
||||
_.each(this.group_field, function (res) {
|
||||
result[res] = (typeof result[res] == 'object') ? result[res][1] : result[res];
|
||||
group_key.push(result[res]);
|
||||
});
|
||||
}else{
|
||||
group_key.push(result[this.group_field]);
|
||||
}
|
||||
var column_key = result[this.chart_info_fields] + "_" + group_key;
|
||||
var column_descriptor = {};
|
||||
if (graph_data[column_key] == undefined) {
|
||||
column_descriptor[this.operator_field] = result[this.operator_field];
|
||||
if (this.operator.length > 1) {
|
||||
column_descriptor[this.operator_field_one] = result[this.operator_field_one];
|
||||
}
|
||||
column_descriptor[this.chart_info_fields] = result[this.chart_info_fields];
|
||||
if(this.group_field.length){
|
||||
_.each(this.group_field, function (res) {
|
||||
column_descriptor[res] = (typeof result[res] == 'object') ? result[res][1] : result[res];
|
||||
});
|
||||
}
|
||||
var attrs = field.attrs;
|
||||
if (attrs.group) {
|
||||
this.group_field = attrs.name;
|
||||
} else if(!this.abscissa) {
|
||||
this.first_field = this.abscissa = attrs.name;
|
||||
} else {
|
||||
column_descriptor = graph_data[column_key];
|
||||
column_descriptor[this.operator_field] += result[this.operator_field];
|
||||
if (this.operator.length > 1) {
|
||||
column_descriptor[this.operator_field_one] += result[this.operator_field_one];
|
||||
}
|
||||
this.columns.push({
|
||||
name: attrs.name,
|
||||
operator: attrs.operator || '+'
|
||||
});
|
||||
}
|
||||
graph_data[column_key] = column_descriptor;
|
||||
}, this);
|
||||
this.ordinate = this.columns[0].name;
|
||||
|
||||
this.dataset.read_slice(
|
||||
this.list_fields(), {}, $.proxy(this, 'schedule_chart'));
|
||||
},
|
||||
schedule_chart: function(results) {
|
||||
var self = this;
|
||||
this.$element.html(QWeb.render("GraphView", {
|
||||
"fields_view": this.fields_view,
|
||||
"chart": this.chart,
|
||||
'element_id': this.element_id
|
||||
}));
|
||||
|
||||
var fields = _(this.columns).pluck('name').concat([this.abscissa]);
|
||||
if (this.group_field) { fields.push(this.group_field); }
|
||||
// transform search result into usable records (convert from OpenERP
|
||||
// value shapes to usable atomic types
|
||||
var records = _(results).map(function (result) {
|
||||
var point = {};
|
||||
_(result).each(function (value, field) {
|
||||
if (!_(fields).contains(field)) { return; }
|
||||
if (value === false) { point[field] = false; return; }
|
||||
switch (self.fields[field].type) {
|
||||
case 'selection':
|
||||
point[field] = _(self.fields[field].selection).detect(function (choice) {
|
||||
return choice[0] === value;
|
||||
})[1];
|
||||
break;
|
||||
case 'many2one':
|
||||
point[field] = value[1];
|
||||
break;
|
||||
case 'integer': case 'float': case 'char':
|
||||
case 'date': case 'datetime':
|
||||
point[field] = value;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
"Unknown field type " + self.fields[field].type
|
||||
+ "for field " + field + " (" + value + ")");
|
||||
}
|
||||
});
|
||||
return point;
|
||||
});
|
||||
// aggregate data, because dhtmlx is crap. Aggregate on abscissa field,
|
||||
// leave split on group field => max m*n records where m is the # of
|
||||
// values for the abscissa and n is the # of values for the group field
|
||||
var graph_data = [];
|
||||
_(records).each(function (record) {
|
||||
var abscissa = record[self.abscissa],
|
||||
group = record[self.group_field];
|
||||
var r = _(graph_data).detect(function (potential) {
|
||||
return potential[self.abscissa] === abscissa
|
||||
&& (!self.group_field
|
||||
|| potential[self.group_field] === group);
|
||||
});
|
||||
var datapoint = r || {};
|
||||
|
||||
datapoint[self.abscissa] = abscissa;
|
||||
if (self.group_field) { datapoint[self.group_field] = group; }
|
||||
_(self.columns).each(function (column) {
|
||||
var val = record[column.name],
|
||||
aggregate = datapoint[column.name];
|
||||
switch(column.operator) {
|
||||
case '+':
|
||||
datapoint[column.name] = (aggregate || 0) + val;
|
||||
return;
|
||||
case '*':
|
||||
datapoint[column.name] = (aggregate || 1) * val;
|
||||
return;
|
||||
case 'min':
|
||||
datapoint[column.name] = (aggregate || Infinity) > val
|
||||
? val
|
||||
: aggregate;
|
||||
return;
|
||||
case 'max':
|
||||
datapoint[column.name] = (aggregate || -Infinity) < val
|
||||
? val
|
||||
: aggregate;
|
||||
}
|
||||
});
|
||||
|
||||
if (!r) { graph_data.push(datapoint); }
|
||||
});
|
||||
graph_data = _(graph_data).sortBy(function (point) {
|
||||
return point[self.abscissa] + '[[--]]' + point[self.group_field];
|
||||
});
|
||||
|
||||
if (this.chart == 'bar') {
|
||||
return this.schedule_bar(_.values(graph_data));
|
||||
return this.schedule_bar(graph_data);
|
||||
} else if (this.chart == "pie") {
|
||||
return this.schedule_pie(_.values(graph_data));
|
||||
return this.schedule_pie(graph_data);
|
||||
}
|
||||
},
|
||||
|
||||
schedule_bar: function(results) {
|
||||
var self = this;
|
||||
var view_chart = '';
|
||||
var group_list = [];
|
||||
var legend_list = [];
|
||||
var newkey = '', newkey_one;
|
||||
var string_legend = '';
|
||||
|
||||
if((self.group_field.length) && (this.operator.length <= 1)){
|
||||
view_chart = self.orientation == 'horizontal'? 'stackedBarH' : 'stackedBar';
|
||||
}else{
|
||||
view_chart = self.orientation == 'horizontal'? 'barH' : 'bar';
|
||||
}
|
||||
|
||||
_.each(results, function (result) {
|
||||
if ((self.group_field.length) && (this.operator.length <= 1)) {
|
||||
var legend_key = '';
|
||||
_.each(self.group_field, function (res) {
|
||||
result[res] = (typeof result[res] == 'object') ? result[res][1] : result[res];
|
||||
legend_key += result[res];
|
||||
});
|
||||
newkey = legend_key.replace(/\s+/g,'_').replace(/[^a-zA-Z 0-9]+/g,'_');
|
||||
string_legend = legend_key;
|
||||
} else {
|
||||
newkey = string_legend = "val";
|
||||
}
|
||||
|
||||
if (_.contains(group_list, newkey) && _.contains(legend_list, string_legend)) {
|
||||
return;
|
||||
}
|
||||
group_list.push(newkey);
|
||||
legend_list.push(string_legend);
|
||||
|
||||
if (this.operator.length > 1) {
|
||||
newkey_one = "val1";
|
||||
group_list.push(newkey_one);
|
||||
legend_list.push(newkey_one);
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (group_list.length <=1){
|
||||
group_list = [];
|
||||
legend_list = [];
|
||||
newkey = string_legend = "val";
|
||||
group_list.push(newkey);
|
||||
legend_list.push(string_legend);
|
||||
}
|
||||
|
||||
var abscissa_data = {};
|
||||
_.each(results, function (result) {
|
||||
var label = result[self.chart_info_fields],
|
||||
section = {};
|
||||
if ((self.group_field.length) && (group_list.length > 1) && (self.operator.length <= 1)){
|
||||
var legend_key_two = '';
|
||||
_.each(self.group_field, function (res) {
|
||||
result[res] = (typeof result[res] == 'object') ? result[res][1] : result[res];
|
||||
legend_key_two += result[res];
|
||||
});
|
||||
newkey = legend_key_two.replace(/\s+/g,'_').replace(/[^a-zA-Z 0-9]+/g,'_');
|
||||
}else{
|
||||
newkey = "val";
|
||||
}
|
||||
if (abscissa_data[label] == undefined){
|
||||
section[self.chart_info_fields] = label;
|
||||
_.each(group_list, function (group) {
|
||||
section[group] = 0;
|
||||
});
|
||||
} else {
|
||||
section = abscissa_data[label];
|
||||
}
|
||||
section[newkey] = result[self.operator_field];
|
||||
if (self.operator.length > 1){
|
||||
section[newkey_one] = result[self.operator_field_one];
|
||||
}
|
||||
abscissa_data[label] = section;
|
||||
});
|
||||
|
||||
//for legend color
|
||||
var grp_color = _.map(legend_list, function (group_legend, index) {
|
||||
var legend = {color: COLOR_PALETTE[index]};
|
||||
if (group_legend == "val"){
|
||||
legend['text'] = self.fields[self.operator_field]['string']
|
||||
}else if(group_legend == "val1"){
|
||||
legend['text'] = self.fields[self.operator_field_one]['string']
|
||||
}else{
|
||||
legend['text'] = group_legend;
|
||||
}
|
||||
return legend;
|
||||
});
|
||||
|
||||
//for axis's value and title
|
||||
var max,min,step;
|
||||
var maximum,minimum;
|
||||
if(_.isEmpty(abscissa_data)){
|
||||
max = 9;
|
||||
min = 0;
|
||||
step=1;
|
||||
}else{
|
||||
var max_min = [];
|
||||
_.each(abscissa_data, function (abscissa_datas) {
|
||||
_.each(group_list, function(res){
|
||||
max_min.push(abscissa_datas[res]);
|
||||
});
|
||||
});
|
||||
maximum = Math.max.apply(Math,max_min);
|
||||
minimum = Math.min.apply(Math,max_min);
|
||||
if (maximum == minimum){
|
||||
if (maximum == 0){
|
||||
max = 9;
|
||||
min = 0;
|
||||
step=1;
|
||||
}else if(maximum > 0){
|
||||
max = maximum + (10 - maximum % 10);
|
||||
min = 0;
|
||||
step = Math.round(max/10);
|
||||
}else{
|
||||
max = 0;
|
||||
min = minimum - (10 + minimum % 10);
|
||||
step = Math.round(Math.abs(min)/10);
|
||||
var group_list, view_chart;
|
||||
if (!this.group_field) {
|
||||
view_chart = (this.orientation === 'horizontal') ? 'barH' : 'bar';
|
||||
group_list = _(this.columns).map(function (column, index) {
|
||||
return {
|
||||
group: column.name,
|
||||
text: self.fields[column.name].string,
|
||||
color: COLOR_PALETTE[index % (COLOR_PALETTE.length)]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// dhtmlx handles clustered bar charts (> 1 column per abscissa
|
||||
// value) and stacked bar charts (basically the same but with the
|
||||
// columns on top of one another instead of side by side), but it
|
||||
// does not handle clustered stacked bar charts
|
||||
if (this.columns.length > 1) {
|
||||
this.$element.text(
|
||||
'OpenERP Web does not support combining grouping and '
|
||||
+ 'multiple columns in graph at this time.');
|
||||
throw new Error(
|
||||
'dhtmlx can not handle columns counts of that magnitude');
|
||||
}
|
||||
// transform series for clustered charts into series for stacked
|
||||
// charts
|
||||
view_chart = (this.orientation === 'horizontal')
|
||||
? 'stackedBarH' : 'stackedBar';
|
||||
group_list = _(results).chain()
|
||||
.pluck(this.group_field)
|
||||
.uniq()
|
||||
.map(function (value, index) {
|
||||
return {
|
||||
group: self.ordinate + '_' +
|
||||
value.toLowerCase().replace(/\s/g, '_'),
|
||||
text: value,
|
||||
color: COLOR_PALETTE[index % COLOR_PALETTE.length]
|
||||
};
|
||||
}).value();
|
||||
|
||||
results = _(results).chain()
|
||||
.groupBy(function (record) { return record[self.abscissa]; })
|
||||
.map(function (records) {
|
||||
var r = {};
|
||||
// second argument is coerced to a str, no good for boolean
|
||||
r[self.abscissa] = records[0][self.abscissa];
|
||||
_(records).each(function (record) {
|
||||
var key = _.sprintf('%s_%s',
|
||||
self.ordinate,
|
||||
record[self.group_field].toLowerCase().replace(/\s/g, '_'));
|
||||
r[key] = record[self.ordinate];
|
||||
});
|
||||
return r;
|
||||
})
|
||||
.value();
|
||||
}
|
||||
|
||||
var abscissa_description = {
|
||||
template: self.chart_info_fields,
|
||||
title: "<b>"+self.x_title+"</b>"
|
||||
title: "<b>" + this.fields[this.abscissa].string + "</b>",
|
||||
template: function (obj) {
|
||||
return obj[self.abscissa] || 'Undefined';
|
||||
}
|
||||
};
|
||||
|
||||
var ordinate_description = {
|
||||
lines: true,
|
||||
title: "<b>"+self.y_title+"</b>",
|
||||
start: min,
|
||||
step: step,
|
||||
end: max
|
||||
title: "<b>" + this.fields[this.ordinate].string + "</b>"
|
||||
};
|
||||
|
||||
var x_axis, y_axis, tooltip;
|
||||
if (self.orientation == 'horizontal'){
|
||||
x_axis = ordinate_description;
|
||||
y_axis = abscissa_description;
|
||||
}else{
|
||||
x_axis = abscissa_description;
|
||||
y_axis = ordinate_description;
|
||||
var x_axis, y_axis;
|
||||
if (self.orientation == 'horizontal') {
|
||||
x_axis = ordinate_description;
|
||||
y_axis = abscissa_description;
|
||||
} else {
|
||||
x_axis = abscissa_description;
|
||||
y_axis = ordinate_description;
|
||||
}
|
||||
tooltip = self.chart_info_fields;
|
||||
|
||||
|
||||
var bar_chart = new dhtmlXChart({
|
||||
view: view_chart,
|
||||
container: self.elem_id+"-barchart",
|
||||
value:"#"+group_list[0]+"#",
|
||||
container: this.element_id+"-barchart",
|
||||
value:"#"+group_list[0].group+"#",
|
||||
gradient: "3d",
|
||||
border: false,
|
||||
width: 1024,
|
||||
tooltip:{
|
||||
template:"#"+tooltip+"#"+","+grp_color[0]['text']+"="+"#"+group_list[0]+"#"
|
||||
template: _.sprintf("#%s#, %s=#%s#",
|
||||
this.abscissa, group_list[0].text, group_list[0].group)
|
||||
},
|
||||
radius: 0,
|
||||
color:grp_color[0]['color'],
|
||||
color:group_list[0].color,
|
||||
origin:0,
|
||||
xAxis:{
|
||||
template:function(obj){
|
||||
if(x_axis['template']){
|
||||
var val = obj[x_axis['template']];
|
||||
val = (typeof val == 'object')?val[1]:(!val?'Undefined':val);
|
||||
if(val.length > 12){
|
||||
val = val.substring(0,12);
|
||||
}
|
||||
return val;
|
||||
}else{
|
||||
return obj;
|
||||
}
|
||||
},
|
||||
title:x_axis['title'],
|
||||
lines:x_axis['lines']
|
||||
},
|
||||
yAxis:{
|
||||
template:function(obj){
|
||||
if(y_axis['template']){
|
||||
var vals = obj[y_axis['template']];
|
||||
vals = (typeof vals == 'object')?vals[1]:(!vals?'Undefined':vals);
|
||||
if(vals.length > 12){
|
||||
vals = vals.substring(0,12);
|
||||
}
|
||||
return vals;
|
||||
}else{
|
||||
return obj;
|
||||
}
|
||||
},
|
||||
title:y_axis['title'],
|
||||
lines: y_axis['lines'],
|
||||
start:y_axis['start'],
|
||||
step:y_axis['step'],
|
||||
end:y_axis['end']
|
||||
},
|
||||
xAxis: x_axis,
|
||||
yAxis: y_axis,
|
||||
padding: {
|
||||
left: 75
|
||||
},
|
||||
legend: {
|
||||
values: grp_color,
|
||||
values: group_list,
|
||||
align:"left",
|
||||
valign:"top",
|
||||
layout: "x",
|
||||
marker:{
|
||||
marker: {
|
||||
type:"round",
|
||||
width:12
|
||||
}
|
||||
}
|
||||
});
|
||||
for (var m = 1; m<group_list.length;m++){
|
||||
var column = group_list[m];
|
||||
if (column.group === this.group_field) { continue; }
|
||||
bar_chart.addSeries({
|
||||
value: "#"+group_list[m]+"#",
|
||||
value: "#"+column.group+"#",
|
||||
tooltip:{
|
||||
template:"#"+tooltip+"#"+","+grp_color[m]['text']+"="+"#"+group_list[m]+"#"
|
||||
template: _.sprintf("#%s#, %s=#%s#",
|
||||
this.abscissa, column.text, column.group)
|
||||
},
|
||||
color: grp_color[m]['color']
|
||||
color: column.color
|
||||
});
|
||||
}
|
||||
bar_chart.parse(_.values(abscissa_data), "json");
|
||||
jQuery("#"+self.elem_id+"-barchart").height(jQuery("#"+self.elem_id+"-barchart").height()+50);
|
||||
bar_chart.parse(results, "json");
|
||||
jQuery("#"+this.element_id+"-barchart").height(jQuery("#"+this.element_id+"-barchart").height()+50);
|
||||
bar_chart.attachEvent("onItemClick", function(id) {
|
||||
self.open_list_view(bar_chart.get(id));
|
||||
});
|
||||
|
@ -383,15 +303,15 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
|
|||
var self = this;
|
||||
var chart = new dhtmlXChart({
|
||||
view:"pie3D",
|
||||
container:self.elem_id+"-piechart",
|
||||
value:"#"+self.operator_field+"#",
|
||||
container:self.element_id+"-piechart",
|
||||
value:"#"+self.ordinate+"#",
|
||||
pieInnerText:function(obj) {
|
||||
var sum = chart.sum("#"+self.operator_field+"#");
|
||||
var val = obj[self.operator_field] / sum * 100 ;
|
||||
var sum = chart.sum("#"+self.ordinate+"#");
|
||||
var val = obj[self.ordinate] / sum * 100 ;
|
||||
return val.toFixed(1) + "%";
|
||||
},
|
||||
tooltip:{
|
||||
template:"#"+self.chart_info_fields+"#"+"="+"#"+self.operator_field+"#"
|
||||
template:"#"+self.abscissa+"#"+"="+"#"+self.ordinate+"#"
|
||||
},
|
||||
gradient:"3d",
|
||||
height: 20,
|
||||
|
@ -406,9 +326,7 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
|
|||
width:12
|
||||
},
|
||||
template:function(obj){
|
||||
var val = obj[self.chart_info_fields];
|
||||
val = (typeof val == 'object')?val[1]:val;
|
||||
return val;
|
||||
return obj[self.abscissa] || 'Undefined';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -422,20 +340,13 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
|
|||
if($(".dhx_tooltip").is(":visible")) {
|
||||
$(".dhx_tooltip").remove('div');
|
||||
}
|
||||
id = id[this.chart_info_fields];
|
||||
id = id[this.abscissa];
|
||||
if (typeof id == 'object'){
|
||||
id = id[0];
|
||||
}
|
||||
|
||||
var record_id = "";
|
||||
this.dataset.model = this.model;
|
||||
if (typeof this.chart_info_fields == 'object'){
|
||||
record_id = this.chart_info_fields[0];
|
||||
}else{
|
||||
record_id = this.chart_info_fields;
|
||||
}
|
||||
this.dataset.domain = [[record_id, '=', id],['id','in',this.dataset.ids]];
|
||||
var modes = !!modes ? modes.split(",") : ["list", "form", "graph"];
|
||||
var record_id = this.abscissa;
|
||||
var modes = ["list", "form", "graph"];
|
||||
var views = [];
|
||||
_.each(modes, function(mode) {
|
||||
var view = [false, mode];
|
||||
|
@ -446,7 +357,7 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
|
|||
});
|
||||
this.do_action({
|
||||
"res_model" : this.dataset.model,
|
||||
"domain" : this.dataset.domain,
|
||||
"domain" : [[record_id, '=', id], ['id','in',this.dataset.ids]],
|
||||
"views" : views,
|
||||
"type" : "ir.actions.act_window",
|
||||
"auto_search" : true,
|
||||
|
@ -462,18 +373,18 @@ openerp.web_graph.GraphView = openerp.web.View.extend({
|
|||
contexts: contexts,
|
||||
group_by_seq: groupbys
|
||||
}, function (results) {
|
||||
// TODO: handle non-empty results.group_by with read_group
|
||||
if(results.group_by && results.group_by != ''){
|
||||
self.chart_info_fields = results.group_by[0];
|
||||
}else{
|
||||
self.chart_info_fields = self.chart_info;
|
||||
// TODO: handle non-empty results.group_by with read_group?
|
||||
if(!_(results.group_by).isEmpty()){
|
||||
self.abscissa = results.group_by[0];
|
||||
} else {
|
||||
self.abscissa = self.first_field;
|
||||
}
|
||||
self.dataset.context = results.context;
|
||||
self.dataset.domain = results.domain;
|
||||
self.dataset.read_slice([],{}, $.proxy(self, 'load_chart'));
|
||||
self.dataset.read_slice(self.list_fields(), {
|
||||
context: results.context,
|
||||
domain: results.domain
|
||||
}, $.proxy(self, 'schedule_chart'));
|
||||
});
|
||||
}
|
||||
});
|
||||
// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them
|
||||
};
|
||||
// vim:et fdc=0 fdl=0:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<template>
|
||||
<div t-name="GraphView" t-att-id="elem_id+'-'+chart+'chart'"
|
||||
<div t-name="GraphView" t-att-id="element_id+'-'+chart+'chart'"
|
||||
style="height:300px;position:relative;"/>
|
||||
</template>
|
|
@ -5,4 +5,5 @@
|
|||
"js": ["static/*/*.js", "static/*/js/*.js"],
|
||||
"css": [],
|
||||
'active': False,
|
||||
'web_preload': False,
|
||||
}
|
||||
|
|
|
@ -13,6 +13,13 @@ openerp.web.SearchView = openerp.web.SearchView.extend({
|
|||
|
||||
// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them
|
||||
|
||||
openerp.web.Login = openerp.web.Login.extend({
|
||||
start: function() {
|
||||
console.log('Hello there');
|
||||
this._super.apply(this,arguments);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// vim:et fdc=0 fdl=0:
|
||||
|
|
|
@ -1,8 +1,21 @@
|
|||
.openerp .oe_kanban_view .oe_column {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.openerp .oe_vertical_text {
|
||||
writing-mode:tb-rl;
|
||||
-webkit-transform:rotate(90deg);
|
||||
-moz-transform:rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
display:block;
|
||||
width:30px;
|
||||
height:20px;
|
||||
align:center;
|
||||
font-size:24px;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .ui-sortable-placeholder {
|
||||
border: 1px dotted black;
|
||||
visibility: visible !important;
|
||||
|
@ -14,6 +27,10 @@
|
|||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.openerp .oe_kanban_view .fold-columns-icon {
|
||||
float: left;
|
||||
padding: 2px 2px 0 0;
|
||||
}
|
||||
.openerp .oe_kanban_action_button {
|
||||
height: 22px;
|
||||
margin: 0;
|
||||
|
@ -200,4 +217,3 @@
|
|||
.openerp .oe_kanban_color_alert .oe_kanban_color_border {
|
||||
border-color: #c00 !important;
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 761 B |
|
@ -18,6 +18,8 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
this.all_display_data = false;
|
||||
this.groups = [];
|
||||
this.qweb = new QWeb2.Engine();
|
||||
this.aggregates = {};
|
||||
this.NO_OF_COLUMNS = 3;
|
||||
if (this.options.action_views_ids.form) {
|
||||
this.form_dialog = new openerp.web.FormDialog(this, {}, this.options.action_views_ids.form, dataset).start();
|
||||
this.form_dialog.on_form_dialog_saved.add_last(this.on_record_saved);
|
||||
|
@ -36,8 +38,17 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
}
|
||||
},
|
||||
add_qweb_template: function() {
|
||||
var group_operator = ["avg", "max", "min", "sum", "count"]
|
||||
for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) {
|
||||
var child = this.fields_view.arch.children[i];
|
||||
if (child.tag === "field") {
|
||||
for(j=0, jj=group_operator.length; j < jj; j++) {
|
||||
if (child.attrs[group_operator[j]]) {
|
||||
this.aggregates[child.attrs.name] = child.attrs[group_operator[j]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (child.tag === "templates") {
|
||||
this.transform_qweb_template(child);
|
||||
this.qweb.add_template(openerp.web.json_node_to_xml(child));
|
||||
|
@ -118,14 +129,33 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
}
|
||||
}
|
||||
},
|
||||
on_show_data: function(data) {
|
||||
sort_group: function (first, second) {
|
||||
if (first.header && second.header)
|
||||
{
|
||||
first = first.header.toLowerCase();
|
||||
second = second.header.toLowerCase();
|
||||
if (first > second) return 1;
|
||||
else if (first < second) return -1;
|
||||
else return 0;
|
||||
}
|
||||
else return 0;
|
||||
},
|
||||
on_show_data: function() {
|
||||
var self = this;
|
||||
this.$element.html(QWeb.render("KanbanView", {"data": data}));
|
||||
if (!this.group_by.length) {
|
||||
this.do_record_group();
|
||||
}
|
||||
self.all_display_data.sort(this.sort_group);
|
||||
this.$element.html(QWeb.render("KanbanView", {"data": self.all_display_data}));
|
||||
this.on_reload_kanban();
|
||||
this.$element.find(".oe_vertical_text").hide();
|
||||
var drag_handel = false;
|
||||
if (this.$element.find(".oe_kanban_draghandle").length > 0) {
|
||||
drag_handel = ".oe_kanban_draghandle";
|
||||
}
|
||||
if (!this.group_by.length) {
|
||||
drag_handel = true;
|
||||
}
|
||||
this.$element.find(".oe_column").sortable({
|
||||
connectWith: ".oe_column",
|
||||
handle : drag_handel,
|
||||
|
@ -134,36 +164,44 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
self.source_index['column'] = ui.item.parent().attr('id');
|
||||
},
|
||||
stop: self.on_receive_record,
|
||||
scroll: false
|
||||
});
|
||||
this.$element.find(".oe_column").disableSelection()
|
||||
this.$element.find('button.oe_kanban_button_new').click(this.do_add_record);
|
||||
this.$element.find(".fold-columns-icon").click(function(event) {
|
||||
self.do_fold_unfold_columns(event, this.id);
|
||||
});
|
||||
},
|
||||
on_button_click: function (button_attrs, record_id) {
|
||||
var self = this;
|
||||
if (this.groups.length) {
|
||||
_.each(this.groups, function (group) {
|
||||
group.list([],
|
||||
function (groups) {},
|
||||
function (dataset) {
|
||||
dataset.read_slice([], {}, function(records) {
|
||||
var index = parseInt(_.indexOf(dataset.ids, record_id));
|
||||
if(index >= 0) {
|
||||
self.on_confirm_click(dataset, button_attrs, index, record_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
var index = parseInt(_.indexOf(self.dataset.ids, record_id));
|
||||
if (index >= 0) {
|
||||
_.extend(self.dataset, {domain: self.domain, context: self.context});
|
||||
self.on_confirm_click(self.dataset, button_attrs, index, record_id);
|
||||
do_fold_unfold_columns: function(event, element_id) {
|
||||
var column_id = "column_" + element_id;
|
||||
var column_element = this.$element.find("#" + column_id + " .oe_fold_column");
|
||||
if (column_element.is(":hidden")) {
|
||||
this.$element.find("#" + column_id).find("img.fold-columns-icon").attr('src', '/web_kanban/static/src/img/minus-icon.png');
|
||||
column_element.show();
|
||||
this.$element.find("#" + column_id + ".oe_table_column").css("width",Math.round(99 / this.all_display_data.length) + "%");
|
||||
this.$element.find("#" + column_id + ".oe_vertical_text").hide();
|
||||
}
|
||||
else{
|
||||
this.$element.find("#" + column_id).find("img.fold-columns-icon").attr('src', '/web_kanban/static/src/img/plus-icon.png');
|
||||
column_element.hide();
|
||||
this.$element.find("#" + column_id + ".oe_table_column").css("width","0.5%");
|
||||
this.$element.find("#" + column_id + ".oe_vertical_text").show();
|
||||
}
|
||||
|
||||
},
|
||||
do_record_group: function() {
|
||||
if (this.NO_OF_COLUMNS && this.all_display_data.length > 0) {
|
||||
var records = this.all_display_data[0].records;
|
||||
var record_per_group = Math.round((records).length / this.NO_OF_COLUMNS);
|
||||
this.all_display_data = [];
|
||||
for (var i=0, ii=this.NO_OF_COLUMNS; i < ii; i++) {
|
||||
this.all_display_data.push({'records': records.slice(0,record_per_group), 'value':i, 'header' : false, 'ids':[]});
|
||||
records.splice(0,record_per_group);
|
||||
}
|
||||
}
|
||||
},
|
||||
on_confirm_click: function (dataset, button_attrs, index, record_id) {
|
||||
this.on_execute_button_click(dataset, button_attrs, record_id);
|
||||
on_button_click: function (button_attrs, record_id) {
|
||||
this.on_execute_button_click(this.dataset, button_attrs, record_id);
|
||||
},
|
||||
do_add_record: function() {
|
||||
this.dataset.index = null;
|
||||
|
@ -179,8 +217,7 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
},
|
||||
on_record_saved: function(r) {
|
||||
var id = this.form_dialog.form.datarecord.id;
|
||||
// TODO fme: reload record instead of all. need refactoring
|
||||
this.do_actual_search();
|
||||
this.on_reload_record(id);
|
||||
},
|
||||
do_change_color: function(record_id, $e) {
|
||||
var self = this,
|
||||
|
@ -198,12 +235,41 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
var data = {};
|
||||
data[$e.data('name')] = $(this).data('color');
|
||||
self.dataset.write(id, data, {}, function() {
|
||||
// TODO fme: reload record instead of all. need refactoring
|
||||
self.do_actual_search();
|
||||
self.on_reload_record(id);
|
||||
});
|
||||
$cpicker.remove();
|
||||
});
|
||||
},
|
||||
/**
|
||||
Reload one record in view.
|
||||
record_id : reload record id.
|
||||
*/
|
||||
on_reload_record: function (record_id){
|
||||
var self = this;
|
||||
this.dataset.read_ids([record_id], [], function (records) {
|
||||
if (records.length > 0) {
|
||||
for (var i=0, ii=self.all_display_data.length; i < ii; i++) {
|
||||
for(j=0, jj=self.all_display_data[i].records.length; j < jj; j++) {
|
||||
if (self.all_display_data[i].records[j].id == record_id) {
|
||||
_.extend(self.all_display_data[i].records[j], records[0]);
|
||||
self.$element.find("#main_" + record_id).children().remove();
|
||||
self.$element.find("#main_" + record_id).append(self.qweb.render('kanban-box', {
|
||||
record: self.do_transform_record(self.all_display_data[i].records[j]),
|
||||
kanban_color: self.kanban_color,
|
||||
kanban_gravatar: self.kanban_gravatar
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.$element.find("#main_" + record_id + " .oe_kanban_action").click(self.on_action_clicked);
|
||||
self.$element.find("#main_" + record_id + " .oe_kanban_box_show_onclick_trigger").click(function() {
|
||||
$(this).parent("#main_" + record_id + " .oe_kanban_box").find(".oe_kanban_box_show_onclick").toggle();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
do_delete: function (id) {
|
||||
var self = this;
|
||||
return $.when(this.dataset.unlink([id])).then(function () {
|
||||
|
@ -228,16 +294,7 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
this.do_execute_action(
|
||||
button_attrs, dataset,
|
||||
record_id, function () {
|
||||
var count = 1;
|
||||
_.each(self.all_display_data, function(data, index) {
|
||||
self.dataset.read_ids( data.ids, [], function(records){
|
||||
self.all_display_data[index].records = records;
|
||||
if(self.all_display_data.length == count) {
|
||||
self.do_actual_search();
|
||||
}
|
||||
count++;
|
||||
});
|
||||
});
|
||||
self.do_actual_search();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
@ -249,9 +306,8 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
if (!ui.item.attr("id")) {
|
||||
return false;
|
||||
}
|
||||
// TODO fme: check what was this sequence
|
||||
if (self.fields_view.fields.sequence && (self.source_index.index >= 0 && self.source_index.index != from) ||
|
||||
(self.source_index.column && self.source_index.column != ui.item.parent().attr('id'))) {
|
||||
if (self.fields_view.fields.sequence != undefined && ((self.source_index.index >= 0 && self.source_index.index != from) ||
|
||||
(self.source_index.column && self.source_index.column != ui.item.parent().attr('id')))) {
|
||||
var child_record = ui.item.parent().children();
|
||||
var data, sequence = 1, index = to;
|
||||
child_record.splice(0, to);
|
||||
|
@ -338,9 +394,6 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
kanban_gravatar: self.kanban_gravatar
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
self.$element.find("#column_" + data.value).remove();
|
||||
self.all_display_data.splice(index, 1);
|
||||
}
|
||||
});
|
||||
this.$element.find('.oe_kanban_action').click(this.on_action_clicked);
|
||||
|
@ -377,8 +430,8 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
do_search: function (domains, contexts, group_by) {
|
||||
var self = this;
|
||||
this.rpc('/web/session/eval_domain_and_context', {
|
||||
domains: domains,
|
||||
contexts: contexts,
|
||||
domains: [this.dataset.get_domain()].concat(domains),
|
||||
contexts: [this.dataset.get_context()].concat(contexts),
|
||||
group_by_seq: group_by
|
||||
}, function (results) {
|
||||
self.domain = results.domain;
|
||||
|
@ -392,23 +445,27 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
group_by = self.group_by;
|
||||
if (!group_by.length && this.fields_view.arch.attrs.default_group_by) {
|
||||
group_by = [this.fields_view.arch.attrs.default_group_by];
|
||||
self.group_by = group_by;
|
||||
}
|
||||
self.datagroup = new openerp.web.DataGroup(self, self.model, self.domain, self.context, group_by);
|
||||
self.dataset.context = self.context;
|
||||
self.dataset.domain = self.domain;
|
||||
self.datagroup.list([],
|
||||
self.datagroup.list(_.keys(self.fields_view.fields),
|
||||
function (groups) {
|
||||
self.groups = groups;
|
||||
self.do_render_group(groups);
|
||||
if (groups.length) {
|
||||
self.do_render_group(groups);
|
||||
}
|
||||
else {
|
||||
self.all_display_data = [];
|
||||
self.on_show_data();
|
||||
}
|
||||
},
|
||||
function (dataset) {
|
||||
self.domain = dataset.domain;
|
||||
self.context = dataset.context;
|
||||
self.groups = [];
|
||||
self.dataset.read_slice([], {}, function(records) {
|
||||
self.all_display_data = [{'records': records, 'value':false, 'header' : false, 'ids': self.dataset.ids}];
|
||||
self.dataset.read_slice([], {'domain': self.domain, 'context': self.context}, function(records) {
|
||||
if (records.length) self.all_display_data = [{'records': records, 'value':false, 'header' : false, 'ids': self.dataset.ids}];
|
||||
else self.all_display_data = [];
|
||||
self.$element.find(".oe_kanban_view").remove();
|
||||
self.on_show_data(self.all_display_data);
|
||||
self.on_show_data();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -417,8 +474,6 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
this.all_display_data = [];
|
||||
var self = this;
|
||||
_.each(datagroups, function (group) {
|
||||
self.dataset.context = group.context;
|
||||
self.dataset.domain = group.domain;
|
||||
var group_name = group.value;
|
||||
var group_value = group.value;
|
||||
if (!group.value) {
|
||||
|
@ -428,11 +483,15 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
|
|||
group_name = group.value[1];
|
||||
group_value = group.value[0];
|
||||
}
|
||||
self.dataset.read_slice([], {}, function(records) {
|
||||
self.all_display_data.push({"value" : group_value, "records": records, 'header':group_name, 'ids': self.dataset.ids});
|
||||
var group_aggregates = {};
|
||||
_.each(self.aggregates, function(value, key) {
|
||||
group_aggregates[value] = group.aggregates[key];
|
||||
});
|
||||
self.dataset.read_slice([], {'domain': group.domain, 'context': group.context}, function(records) {
|
||||
self.all_display_data.push({"value" : group_value, "records": records, 'header':group_name, 'ids': self.dataset.ids, 'aggregates': group_aggregates});
|
||||
if (datagroups.length == self.all_display_data.length) {
|
||||
self.$element.find(".oe_kanban_view").remove();
|
||||
self.on_show_data(self.all_display_data);
|
||||
self.on_show_data();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,15 +9,22 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td t-foreach="data" t-as="columns" class="oe_table_column oe_column_heading" t-att-id="'column_' + columns.value">
|
||||
<t t-if="columns.value" t-esc="columns.header"/>
|
||||
<td t-foreach="data" t-as="columns" class="oe_table_column" t-att-id="'column_' + columns.value">
|
||||
<img t-att-id="columns.value" class="fold-columns-icon" src="/web_kanban/static/src/img/minus-icon.png"/>
|
||||
<div class="oe_fold_column" t-att-id="'column_' + columns.value">
|
||||
<div class="oe_column_heading" t-if="columns.value and columns.header">
|
||||
<t t-esc="columns.header"/>
|
||||
</div>
|
||||
<div t-foreach="columns.aggregates or {}" t-as="aggregate">
|
||||
<i><u><t t-esc="aggregate"/>:</u> <t t-esc="aggregate_value"/></i>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td t-foreach="data" t-as="columns" class="oe_table_column" t-att-id="'column_' + columns.value" t-attf-style="width: #{Math.round(99 / data.length)}%">
|
||||
<div class="oe_column" t-att-id="'column_' + columns.value">
|
||||
<div t-foreach="columns.records" t-as="record" class="oe_kanban_record" t-att-id="'main_' + record.id"/>
|
||||
</div>
|
||||
<td t-foreach="data" t-as="columns" class="oe_column oe_table_column" t-att-id="'column_' + columns.value" t-attf-style="width: #{Math.round(99 / data.length)}%">
|
||||
<p t-if="columns.header" class="oe_vertical_text" t-att-id="'column_' + columns.value"><t t-esc="columns.header"/></p>
|
||||
<div t-foreach="columns.records" t-as="record" class="oe_fold_column oe_kanban_record" t-att-id="'main_' + record.id"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Tests",
|
||||
"version": "2.0",
|
||||
"depends": [],
|
||||
"js": ["static/src/js/*.js"],
|
||||
"css": ['static/src/css/*.css'],
|
||||
'active': True,
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.oe-bunchaforms > div {
|
||||
float: left;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
openerp.web_tests = function (db) {
|
||||
db.web.client_actions.add(
|
||||
'buncha-forms', 'instance.web_tests.BunchaForms');
|
||||
db.web_tests = {};
|
||||
db.web_tests.BunchaForms = db.web.Widget.extend({
|
||||
init: function (parent) {
|
||||
this._super(parent);
|
||||
this.dataset = new db.web.DataSetSearch(this, 'test.listview.relations');
|
||||
this.form = new db.web.FormView(this, this.dataset, false, {
|
||||
action_buttons: false,
|
||||
pager: false
|
||||
});
|
||||
this.form.registry = db.web.form.readonly;
|
||||
},
|
||||
render: function () {
|
||||
return '<div class="oe-bunchaforms"></div>';
|
||||
},
|
||||
start: function () {
|
||||
$.when(
|
||||
this.dataset.read_slice(),
|
||||
this.form.appendTo(this.$element)).then(this.on_everything_loaded);
|
||||
},
|
||||
on_everything_loaded: function (slice) {
|
||||
var records = slice[0].records;
|
||||
if (!records.length) {
|
||||
this.form.on_record_loaded({});
|
||||
return;
|
||||
}
|
||||
this.form.on_record_loaded(records[0]);
|
||||
_(records.slice(1)).each(function (record, index) {
|
||||
this.dataset.index = index+1;
|
||||
this.form.reposition($('<div>').appendTo(this.$element));
|
||||
this.form.on_record_loaded(record);
|
||||
}, this);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -282,6 +282,18 @@ view managers can correctly communicate with them:
|
|||
defining e.g. the ``start`` method) or at the instance level (in the
|
||||
class's ``init``), though you should generally set it on the class.
|
||||
|
||||
Frequent development tasks
|
||||
--------------------------
|
||||
|
||||
There are a number of tasks which OpenERP Web developers do or will need to
|
||||
perform quite regularly. To make these easier, we have written a few guides
|
||||
to help you get started:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
guides/client-action.rst
|
||||
|
||||
Utility behaviors
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
Creating a new client action
|
||||
============================
|
||||
|
||||
Client actions are the client-side of OpenERP's "Server Actions": instead of
|
||||
allowing for semi-arbitrary code to be executed in the server, they allow
|
||||
for execution of client-customized code.
|
||||
|
||||
On the server side, a client action is an action of type ``ir.actions.client``,
|
||||
which has (at most) two properties: a mandatory ``tag``, which is an arbitrary
|
||||
string by which the client will identify the action, and an optional ``params``
|
||||
which is simply a map of keys and values sent to the client as-is (this way,
|
||||
client actions can be made generic and reused in multiple contexts).
|
||||
|
||||
General Structure
|
||||
-----------------
|
||||
|
||||
In the OpenERP Web code, a client action only requires two pieces of
|
||||
information:
|
||||
|
||||
* Mapping the action's ``tag`` to an OpenERP Web object
|
||||
|
||||
* The OpenERP Web object itself, which must inherit from
|
||||
:js:class:`openerp.web.Widget`
|
||||
|
||||
Our example will be the actual code for the widgets client action (a client
|
||||
action displaying a ``res.widget`` object, used in the homepage dashboard of
|
||||
the web client):
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// Registers the object 'openerp.web_dashboard.Widget' to the client
|
||||
// action tag 'board.home.widgets'
|
||||
openerp.web.client_actions.add(
|
||||
'board.home.widgets', 'openerp.web_dashboard.Widget');
|
||||
// This object inherits from View, but only Widget is required
|
||||
openerp.web_dashboard.Widget = openerp.web.View.extend({
|
||||
template: 'HomeWidget'
|
||||
});
|
||||
|
||||
At this point, the generic ``Widget`` lifecycle takes over, the template is
|
||||
rendered, inserted in the client DOM, bound on the object's ``$element``
|
||||
property and the object is started.
|
||||
|
||||
If the client action takes parameters, these parameters are passed in as a
|
||||
second positional parameter to the constructor:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
init: function (parent, params) {
|
||||
// execute the Widget's init
|
||||
this._super(parent);
|
||||
// board.home.widgets only takes a single param, the identifier of the
|
||||
// res.widget object it should display. Store it for later
|
||||
this.widget_id = params.widget_id;
|
||||
}
|
||||
|
||||
More complex initialization (DOM manipulations, RPC requests, ...) should be
|
||||
performed in the ``start()`` method.
|
||||
|
||||
.. note::
|
||||
As required by ``Widget``'s contract, if ``start`` executes any
|
||||
asynchronous code it should return a ``$.Deferred`` so callers know when
|
||||
it's ready for interaction.
|
||||
|
||||
Although generally speaking client actions are not really interacted with.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
start: function () {
|
||||
return $.when(
|
||||
this._super(),
|
||||
// Simply read the res.widget object this action should display
|
||||
new openerp.web.DataSet(this, 'res.widget').read_ids(
|
||||
[this.widget_id], ['title'], this.on_widget_loaded));
|
||||
}
|
||||
|
||||
The client action can then behave exactly as it wishes to within its root
|
||||
(``this.$element``). In this case, it performs further renderings once its
|
||||
widget's content is retrieved:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
on_widget_loaded: function (widgets) {
|
||||
var widget = widgets[0];
|
||||
var url = _.sprintf(
|
||||
'/web_dashboard/widgets/content?session_id=%s&widget_id=%d',
|
||||
this.session.session_id, widget.id);
|
||||
this.$element.html(QWeb.render('HomeWidget.content', {
|
||||
widget: widget,
|
||||
url: url
|
||||
}));
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"version": 1,
|
||||
"formatters": {
|
||||
"simple": {
|
||||
"format": "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
|
||||
}
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "DEBUG",
|
||||
"formatter": "simple",
|
||||
"stream": "ext://sys.stdout"
|
||||
}
|
||||
},
|
||||
"loggers": {
|
||||
"web": {
|
||||
},
|
||||
"web.common.openerplib": {
|
||||
"level": "INFO"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"level": "DEBUG",
|
||||
"handlers": ["console"]
|
||||
}
|
||||
}
|
|
@ -2,11 +2,13 @@
|
|||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import tempfile
|
||||
import logging
|
||||
import logging.config
|
||||
|
||||
import werkzeug.serving
|
||||
import werkzeug.contrib.fixers
|
||||
|
||||
path_root = os.path.dirname(os.path.abspath(__file__))
|
||||
path_addons = os.path.join(path_root, 'addons')
|
||||
|
@ -14,8 +16,6 @@ if path_addons not in sys.path:
|
|||
sys.path.insert(0, path_addons)
|
||||
|
||||
optparser = optparse.OptionParser()
|
||||
optparser.add_option("-p", "--port", dest="socket_port", default=8002,
|
||||
help="listening port", type="int", metavar="NUMBER")
|
||||
optparser.add_option("-s", "--session-path", dest="session_storage",
|
||||
default=os.path.join(tempfile.gettempdir(), "oe-sessions"),
|
||||
help="directory used for session storage", metavar="DIR")
|
||||
|
@ -25,37 +25,57 @@ optparser.add_option("--server-port", dest="server_port", default=8069,
|
|||
help="OpenERP server port", type="int", metavar="NUMBER")
|
||||
optparser.add_option("--db-filter", dest="dbfilter", default='.*',
|
||||
help="Filter listed database", metavar="REGEXP")
|
||||
optparser.add_option('--addons-path', dest='addons_path', default=path_addons,
|
||||
optparser.add_option('--addons-path', dest='addons_path', default=[path_addons], action='append',
|
||||
help="Path do addons directory", metavar="PATH")
|
||||
optparser.add_option('--no-serve-static', dest='serve_static',
|
||||
default=True, action='store_false',
|
||||
help="Do not serve static files via this server")
|
||||
optparser.add_option('--reloader', dest='reloader',
|
||||
default=False, action='store_true',
|
||||
help="Reload application when python files change")
|
||||
optparser.add_option("--log-level", dest="log_level",
|
||||
default='debug', help="Log level", metavar="LOG_LEVEL")
|
||||
optparser.add_option("--log-config", dest="log_config",
|
||||
default='', help="Log config file", metavar="LOG_CONFIG")
|
||||
optparser.add_option('--multi-threaded', dest='threaded',
|
||||
default=False, action='store_true',
|
||||
help="Use multiple threads to handle requests")
|
||||
optparser.add_option('--load', dest='server_wide_modules', default=['web'], action='append',
|
||||
help="Load a additional module before login (by default only 'web' is loaded)", metavar="MODULE")
|
||||
|
||||
server_options = optparse.OptionGroup(optparser, "Server configuration")
|
||||
server_options.add_option("-p", "--port", dest="socket_port", default=8002,
|
||||
help="listening port", type="int", metavar="NUMBER")
|
||||
server_options.add_option('--reloader', dest='reloader',
|
||||
default=False, action='store_true',
|
||||
help="Reload application when python files change")
|
||||
server_options.add_option('--no-serve-static', dest='serve_static',
|
||||
default=True, action='store_false',
|
||||
help="Do not serve static files via this server")
|
||||
server_options.add_option('--multi-threaded', dest='threaded',
|
||||
default=False, action='store_true',
|
||||
help="Spawn one thread per HTTP request")
|
||||
server_options.add_option('--proxy-mode', dest='proxy_mode',
|
||||
default=False, action='store_true',
|
||||
help="Enable correct behavior when behind a reverse proxy")
|
||||
optparser.add_option_group(server_options)
|
||||
|
||||
logging_opts = optparse.OptionGroup(optparser, "Logging")
|
||||
logging_opts.add_option("--log-level", dest="log_level", type="choice",
|
||||
default='debug', help="Global logging level", metavar="LOG_LEVEL",
|
||||
choices=['debug', 'info', 'warning', 'error', 'critical'])
|
||||
logging_opts.add_option("--log-config", dest="log_config", default=os.path.join(os.path.dirname(__file__), "logging.json"),
|
||||
help="Logging configuration file", metavar="FILE")
|
||||
optparser.add_option_group(logging_opts)
|
||||
|
||||
import web.common.dispatch
|
||||
|
||||
if __name__ == "__main__":
|
||||
(options, args) = optparser.parse_args(sys.argv[1:])
|
||||
options.backend = 'rpc'
|
||||
options.backend = 'xmlrpc'
|
||||
|
||||
os.environ["TZ"] = "UTC"
|
||||
|
||||
if not options.log_config:
|
||||
logging.basicConfig(level=getattr(logging, options.log_level.upper()))
|
||||
if sys.version_info >= (2, 7):
|
||||
with open(options.log_config) as file:
|
||||
dct = json.load(file)
|
||||
logging.config.dictConfig(dct)
|
||||
logging.getLogger("").setLevel(getattr(logging, options.log_level.upper()))
|
||||
else:
|
||||
logging.config.fileConfig(options.log_config)
|
||||
logging.basicConfig(level=getattr(logging, options.log_level.upper()))
|
||||
|
||||
app = web.common.dispatch.Root(options)
|
||||
|
||||
if options.proxy_mode:
|
||||
app = werkzeug.contrib.fixers.ProxyFix(app)
|
||||
|
||||
werkzeug.serving.run_simple(
|
||||
'0.0.0.0', options.socket_port, app,
|
||||
use_reloader=options.reloader, threaded=options.threaded)
|
||||
|
|
Loading…
Reference in New Issue