[MERGE]merged from openerp-web

bzr revid: pan@tinyerp.com-20120829101332-yobjd731lsldwa8q
This commit is contained in:
Anand Patel (OpenERP) 2012-08-29 15:43:32 +05:30
commit c54d505ad3
501 changed files with 29733 additions and 27018 deletions

View File

@ -1,67 +1,71 @@
{
"name" : "Web",
"category": "Hidden",
"description":
'name': 'Web',
'category': 'Hidden',
'description':
"""
OpenERP Web core module.
This module provides the core of the OpenERP Web Client.
OpenERP Web core module.
========================
This module provides the core of the OpenERP Web Client.
""",
"depends" : [],
'depends': [],
'auto_install': True,
'post_load' : 'wsgi_postload',
'post_load': 'wsgi_postload',
'js' : [
"static/lib/datejs/globalization/en-US.js",
"static/lib/datejs/core.js",
"static/lib/datejs/parser.js",
"static/lib/datejs/sugarpak.js",
"static/lib/datejs/extras.js",
"static/lib/jquery/jquery-1.7.2.js",
"static/lib/jquery.MD5/jquery.md5.js",
"static/lib/jquery.form/jquery.form.js",
"static/lib/jquery.validate/jquery.validate.js",
"static/lib/jquery.ba-bbq/jquery.ba-bbq.js",
"static/lib/jquery.blockUI/jquery.blockUI.js",
"static/lib/jquery.ui/js/jquery-ui-1.8.17.custom.min.js",
"static/lib/jquery.ui.timepicker/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/jquery.scrollTo/jquery.scrollTo-min.js",
"static/lib/jquery.tipsy/jquery.tipsy.js",
"static/lib/jquery.textext/jquery.textext.js",
"static/lib/jquery.timeago/jquery.timeago.js",
"static/lib/qweb/qweb2.js",
"static/lib/underscore/underscore.js",
"static/lib/underscore/underscore.string.js",
"static/lib/backbone/backbone.js",
"static/lib/py.js/lib/py.js",
"static/src/js/boot.js",
"static/src/js/corelib.js",
"static/src/js/coresetup.js",
"static/src/js/dates.js",
"static/src/js/formats.js",
"static/src/js/chrome.js",
"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",
"static/src/js/view_list_editable.js",
"static/src/js/view_tree.js",
"static/src/js/view_editor.js"
'static/lib/datejs/globalization/en-US.js',
'static/lib/datejs/core.js',
'static/lib/datejs/parser.js',
'static/lib/datejs/sugarpak.js',
'static/lib/datejs/extras.js',
'static/lib/jquery/jquery-1.7.2.js',
'static/lib/jquery.MD5/jquery.md5.js',
'static/lib/jquery.form/jquery.form.js',
'static/lib/jquery.validate/jquery.validate.js',
'static/lib/jquery.ba-bbq/jquery.ba-bbq.js',
'static/lib/spinjs/spin.js',
'static/lib/jquery.blockUI/jquery.blockUI.js',
'static/lib/jquery.ui/js/jquery-ui-1.8.17.custom.min.js',
'static/lib/jquery.ui.timepicker/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/jquery.scrollTo/jquery.scrollTo-min.js',
'static/lib/jquery.tipsy/jquery.tipsy.js',
'static/lib/jquery.textext/jquery.textext.js',
'static/lib/jquery.timeago/jquery.timeago.js',
'static/lib/qweb/qweb2.js',
'static/lib/underscore/underscore.js',
'static/lib/underscore/underscore.string.js',
'static/lib/backbone/backbone.js',
'static/lib/cleditor/jquery.cleditor.js',
'static/lib/py.js/lib/py.js',
'static/src/js/boot.js',
'static/src/js/corelib.js',
'static/src/js/coresetup.js',
'static/src/js/dates.js',
'static/src/js/formats.js',
'static/src/js/chrome.js',
'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',
'static/src/js/view_list_editable.js',
'static/src/js/view_tree.js',
],
'css' : [
"static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.8.16.custom.css",
"static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css",
"static/lib/jquery.ui.notify/css/ui.notify.css",
"static/lib/jquery.tipsy/tipsy.css",
"static/lib/jquery.textext/jquery.textext.css",
"static/src/css/base.css",
"static/src/css/data_export.css",
"static/src/css/data_import.css",
'css': [
'static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.8.16.custom.css',
'static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css',
'static/lib/jquery.ui.notify/css/ui.notify.css',
'static/lib/jquery.tipsy/tipsy.css',
'static/lib/jquery.textext/jquery.textext.css',
'static/src/css/base.css',
'static/src/css/data_export.css',
'static/src/css/data_import.css',
'static/lib/cleditor/jquery.cleditor.css',
],
'qweb' : [
"static/src/xml/*.xml",
'qweb': [
'static/src/xml/*.xml',
],
}

View File

@ -28,6 +28,7 @@ import werkzeug.wsgi
from . import nonliterals
from . import session
from . import openerplib
import urlparse
__all__ = ['Root', 'jsonrequest', 'httprequest', 'Controller',
'WebRequest', 'JsonRequest', 'HttpRequest']
@ -329,16 +330,32 @@ def httprequest(f):
return http_handler
#----------------------------------------------------------
# OpenERP Web werkzeug Session Managment wraped using with
# OpenERP Web Controller registration with a metaclass
#----------------------------------------------------------
addons_module = {}
addons_manifest = {}
controllers_class = []
controllers_object = {}
controllers_path = {}
class ControllerType(type):
def __init__(cls, name, bases, attrs):
super(ControllerType, cls).__init__(name, bases, attrs)
controllers_class.append(("%s.%s" % (cls.__module__, cls.__name__), cls))
class Controller(object):
__metaclass__ = ControllerType
#----------------------------------------------------------
# OpenERP Web Session context manager
#----------------------------------------------------------
STORES = {}
@contextlib.contextmanager
def session_context(request, storage_path, session_cookie='sessionid'):
def session_context(request, storage_path, session_cookie='httpsessionid'):
session_store, session_lock = STORES.get(storage_path, (None, None))
if not session_store:
session_store = werkzeug.contrib.sessions.FilesystemSessionStore(
storage_path)
session_store = werkzeug.contrib.sessions.FilesystemSessionStore( storage_path)
session_lock = threading.Lock()
STORES[storage_path] = session_store, session_lock
@ -381,7 +398,7 @@ def session_context(request, storage_path, session_cookie='sessionid'):
# note that domains_store and contexts_store are append-only (we
# only ever add items to them), so we can just update one with the
# other to get the right result, if we want to merge the
# ``context`` dict we'll need something smarter
# ``context`` dict we'll need something smarter
in_store = session_store.get(sid)
for k, v in request.session.iteritems():
stored = in_store.get(k)
@ -401,21 +418,29 @@ def session_context(request, storage_path, session_cookie='sessionid'):
session_store.save(request.session)
#----------------------------------------------------------
# OpenERP Web Module/Controller Loading and URL Routing
# OpenERP Web WSGI Application
#----------------------------------------------------------
addons_module = {}
addons_manifest = {}
controllers_class = []
controllers_object = {}
controllers_path = {}
class DisableCacheMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
def start_wrapped(status, headers):
referer = environ.get('HTTP_REFERER', '')
parsed = urlparse.urlparse(referer)
debug = parsed.query.count('debug') >= 1
class ControllerType(type):
def __init__(cls, name, bases, attrs):
super(ControllerType, cls).__init__(name, bases, attrs)
controllers_class.append(("%s.%s" % (cls.__module__, cls.__name__), cls))
new_headers = []
unwanted_keys = ['Last-Modified']
if debug:
new_headers = [('Cache-Control', 'no-cache')]
unwanted_keys += ['Expires', 'Etag', 'Cache-Control']
class Controller(object):
__metaclass__ = ControllerType
for k, v in headers:
if k not in unwanted_keys:
new_headers.append((k, v))
start_response(status, new_headers)
return self.app(environ, start_wrapped)
class Root(object):
"""Root WSGI application for the OpenERP Web Client.
@ -437,23 +462,22 @@ class Root(object):
by the server, will be filtered by this pattern
"""
def __init__(self, options, openerp_addons_namespace=True):
self.root = '/web/webclient/home'
self.config = options
if not hasattr(self.config, 'connector'):
if self.config.backend == 'local':
self.config.connector = LocalConnector()
self.config.connector = session.LocalConnector()
else:
self.config.connector = openerplib.get_connector(
hostname=self.config.server_host, port=self.config.server_port)
self.session_cookie = 'sessionid'
self.httpsession_cookie = 'httpsessionid'
self.addons = {}
static_dirs = self._load_addons(openerp_addons_namespace)
if options.serve_static:
self.dispatch = SuperSharedDataMiddleware(
self.dispatch, static_dirs, cache=False)
app = werkzeug.wsgi.SharedDataMiddleware( self.dispatch, static_dirs)
self.dispatch = DisableCacheMiddleware(app)
if options.session_storage:
if not os.path.exists(options.session_storage):
@ -477,20 +501,12 @@ class Root(object):
request.parameter_storage_class = werkzeug.datastructures.ImmutableDict
request.app = self
if request.path == '/':
params = urllib.urlencode(request.args)
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)
handler = self.find_handler(*(request.path.split('/')[1:]))
if not handler:
response = werkzeug.exceptions.NotFound()
else:
with session_context(request, self.session_storage, self.session_cookie) as session:
with session_context(request, self.session_storage, self.httpsession_cookie) as session:
result = handler( request, self.config)
if isinstance(result, basestring):
@ -500,7 +516,7 @@ class Root(object):
response = result
if hasattr(response, 'set_cookie'):
response.set_cookie(self.session_cookie, session.sid)
response.set_cookie(self.httpsession_cookie, session.sid)
return response(environ, start_response)
@ -549,130 +565,13 @@ class Root(object):
while ps:
c = controllers_path.get(ps)
if c:
m = getattr(c, meth)
if getattr(m, 'exposed', False):
m = getattr(c, meth, None)
if m and getattr(m, 'exposed', False):
_logger.debug("Dispatching to %s %s %s", ps, c, meth)
return m
ps, _slash, meth = ps.rpartition('/')
if not ps and meth:
ps = '/'
return None
class SuperSharedDataMiddleware(werkzeug.wsgi.SharedDataMiddleware):
"""Redefine SharedDataMiddleware to better handle the cache = False directive.
Also desactivate 304 Not Modified headers only when the referer has 'debug' in its
arguments.
"""
def __call__(self, environ, start_response):
import os
import mimetypes
import werkzeug.http
import urlparse
# sanitize the path for non unix systems
cleaned_path = environ.get('PATH_INFO', '').strip('/')
for sep in os.sep, os.altsep:
if sep and sep != '/':
cleaned_path = cleaned_path.replace(sep, '/')
path = '/'.join([''] + [x for x in cleaned_path.split('/')
if x and x != '..'])
file_loader = None
for search_path, loader in self.exports.iteritems():
if search_path == path:
real_filename, file_loader = loader(None)
if file_loader is not None:
break
if not search_path.endswith('/'):
search_path += '/'
if path.startswith(search_path):
real_filename, file_loader = loader(path[len(search_path):])
if file_loader is not None:
break
if file_loader is None or not self.is_allowed(real_filename):
return self.app(environ, start_response)
guessed_type = mimetypes.guess_type(real_filename)
mime_type = guessed_type[0] or self.fallback_mimetype
f, mtime, file_size = file_loader()
etag = self.generate_etag(mtime, file_size, real_filename)
modified = werkzeug.http.is_resource_modified(environ, etag, last_modified=mtime)
headers = [('Date', werkzeug.http.http_date())]
if self.cache:
timeout = self.cache_timeout
headers += [
('Etag', '"%s"' % etag),
('Cache-Control', 'max-age=%d, public' % timeout)
]
if modified:
headers.append(('Expires', werkzeug.http.http_date(time() + timeout)))
else:
headers.append(('Cache-Control', 'no-cache'))
referer = environ.get('HTTP_REFERER', '')
parsed = urlparse.urlparse(referer)
debug = not urlparse.parse_qs(parsed.query).has_key('debug')
# it's important to put it at the end
if not debug and not modified:
f.close()
start_response('304 Not Modified', headers)
return []
headers.extend((
('Content-Type', mime_type),
('Content-Length', str(file_size)),
('Last-Modified', werkzeug.http.http_date(mtime))
))
start_response('200 OK', headers)
return werkzeug.wsgi.wrap_file(environ, f)
class LibException(Exception):
""" Base of all client lib exceptions """
def __init__(self,code=None,message=None):
self.code = code
self.message = message
class ApplicationError(LibException):
""" maps to code: 1, server side: Exception or openerp.exceptions.DeferredException"""
class Warning(LibException):
""" maps to code: 2, server side: openerp.exceptions.Warning"""
class AccessError(LibException):
""" maps to code: 3, server side: openerp.exceptions.AccessError"""
class AccessDenied(LibException):
""" maps to code: 4, server side: openerp.exceptions.AccessDenied"""
class LocalConnector(openerplib.Connector):
"""
A type of connector that uses the XMLRPC protocol.
"""
PROTOCOL = 'local'
def __init__(self):
pass
def send(self, service_name, method, *args):
import openerp
import traceback
import xmlrpclib
code_string = "warning -- %s\n\n%s"
try:
return openerp.netsvc.dispatch_rpc(service_name, method, args)
except openerp.osv.osv.except_osv, e:
# TODO change the except to raise LibException instead of their emulated xmlrpc fault
raise xmlrpclib.Fault(code_string % (e.name, e.value), '')
except openerp.exceptions.Warning, e:
raise xmlrpclib.Fault(code_string % ("Warning", e), '')
except openerp.exceptions.AccessError, e:
raise xmlrpclib.Fault(code_string % ("AccessError", e), '')
except openerp.exceptions.AccessDenied, e:
raise xmlrpclib.Fault('AccessDenied', str(e))
except openerp.exceptions.DeferredException, e:
formatted_info = "".join(traceback.format_exception(*e.traceback))
raise xmlrpclib.Fault(openerp.tools.ustr(e.message), formatted_info)
except Exception, e:
formatted_info = "".join(traceback.format_exception(*(sys.exc_info())))
raise xmlrpclib.Fault(openerp.tools.exception_to_unicode(e), formatted_info)
# vim:et:ts=4:sw=4:

View File

@ -4,11 +4,67 @@ import babel
import dateutil.relativedelta
import logging
import time
import sys
import openerplib
from . import nonliterals
_logger = logging.getLogger(__name__)
#----------------------------------------------------------
# openerplib local connector
#----------------------------------------------------------
class LibException(Exception):
""" Base of all client lib exceptions """
def __init__(self,code=None,message=None):
self.code = code
self.message = message
class ApplicationError(LibException):
""" maps to code: 1, server side: Exception or openerp.exceptions.DeferredException"""
class Warning(LibException):
""" maps to code: 2, server side: openerp.exceptions.Warning"""
class AccessError(LibException):
""" maps to code: 3, server side: openerp.exceptions.AccessError"""
class AccessDenied(LibException):
""" maps to code: 4, server side: openerp.exceptions.AccessDenied"""
class LocalConnector(openerplib.Connector):
"""
A type of connector that uses the XMLRPC protocol.
"""
PROTOCOL = 'local'
def __init__(self):
pass
def send(self, service_name, method, *args):
import openerp
import traceback
import xmlrpclib
code_string = "warning -- %s\n\n%s"
try:
return openerp.netsvc.dispatch_rpc(service_name, method, args)
except openerp.osv.osv.except_osv, e:
# TODO change the except to raise LibException instead of their emulated xmlrpc fault
raise xmlrpclib.Fault(code_string % (e.name, e.value), '')
except openerp.exceptions.Warning, e:
raise xmlrpclib.Fault(code_string % ("Warning", e), '')
except openerp.exceptions.AccessError, e:
raise xmlrpclib.Fault(code_string % ("AccessError", e), '')
except openerp.exceptions.AccessDenied, e:
raise xmlrpclib.Fault('AccessDenied', str(e))
except openerp.exceptions.DeferredException, e:
formatted_info = "".join(traceback.format_exception(*e.traceback))
raise xmlrpclib.Fault(openerp.tools.ustr(e.message), formatted_info)
except Exception, e:
formatted_info = "".join(traceback.format_exception(*(sys.exc_info())))
raise xmlrpclib.Fault(openerp.tools.exception_to_unicode(e), formatted_info)
#----------------------------------------------------------
# OpenERPSession RPC openerp backend access
#----------------------------------------------------------
@ -48,12 +104,6 @@ class OpenERPSession(object):
del state['config']
return state
def openerp_entreprise(self):
if not self._uid:
return False
else:
return self.model('publisher_warranty.contract').status()['status'] == 'full'
def build_connection(self):
conn = openerplib.Connection(self.config.connector, database=self._db, login=self._login,
user_id=self._uid, password=self._password)
@ -68,7 +118,7 @@ class OpenERPSession(object):
self._login = login
self._password = password
def authenticate(self, db, login, password, env):
def authenticate(self, db, login, password, env=None):
# TODO use the openerplib API once it exposes authenticate()
uid = self.proxy('common').authenticate(db, login, password, env)
self.bind(db, uid, login, password)
@ -216,3 +266,5 @@ class OpenERPSession(object):
cdomain = nonliterals.CompoundDomain(domain)
cdomain.session = self
return cdomain.evaluate(context or {})
# vim:et:ts=4:sw=4:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1563
addons/web/i18n/fr_CA.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
// Backbone.js 0.9.1
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
@ -32,7 +32,7 @@
}
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '0.9.1';
Backbone.VERSION = '0.9.2';
// Require Underscore, if we're on the server, and it's not already present.
var _ = root._;
@ -71,6 +71,9 @@
// Backbone.Events
// -----------------
// Regular expression used to split event strings
var eventSplitter = /\s+/;
// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback functions
// to an event; trigger`-ing an event fires all callbacks in succession.
@ -80,89 +83,110 @@
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
Backbone.Events = {
var Events = Backbone.Events = {
// Bind an event, specified by a string name, `ev`, to a `callback`
// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
on: function(events, callback, context) {
var ev;
events = events.split(/\s+/);
var calls = this._callbacks || (this._callbacks = {});
while (ev = events.shift()) {
// Create an immutable callback list, allowing traversal during
// modification. The tail is an empty object that will always be used
// as the next node.
var list = calls[ev] || (calls[ev] = {});
var tail = list.tail || (list.tail = list.next = {});
tail.callback = callback;
tail.context = context;
list.tail = tail.next = {};
var calls, event, node, tail, list;
if (!callback) return this;
events = events.split(eventSplitter);
calls = this._callbacks || (this._callbacks = {});
// Create an immutable callback list, allowing traversal during
// modification. The tail is an empty object that will always be used
// as the next node.
while (event = events.shift()) {
list = calls[event];
node = list ? list.tail : {};
node.next = tail = {};
node.context = context;
node.callback = callback;
calls[event] = {tail: tail, next: list ? list.next : node};
}
return this;
},
// Remove one or many callbacks. If `context` is null, removes all callbacks
// with that function. If `callback` is null, removes all callbacks for the
// event. If `ev` is null, removes all bound callbacks for all events.
// event. If `events` is null, removes all bound callbacks for all events.
off: function(events, callback, context) {
var ev, calls, node;
if (!events) {
var event, calls, node, tail, cb, ctx;
// No events, or removing *all* events.
if (!(calls = this._callbacks)) return;
if (!(events || callback || context)) {
delete this._callbacks;
} else if (calls = this._callbacks) {
events = events.split(/\s+/);
while (ev = events.shift()) {
node = calls[ev];
delete calls[ev];
if (!callback || !node) continue;
// Create a new list, omitting the indicated event/context pairs.
while ((node = node.next) && node.next) {
if (node.callback === callback &&
(!context || node.context === context)) continue;
this.on(ev, node.callback, node.context);
return this;
}
// Loop through the listed events and contexts, splicing them out of the
// linked list of callbacks if appropriate.
events = events ? events.split(eventSplitter) : _.keys(calls);
while (event = events.shift()) {
node = calls[event];
delete calls[event];
if (!node || !(callback || context)) continue;
// Create a new list, omitting the indicated callbacks.
tail = node.tail;
while ((node = node.next) !== tail) {
cb = node.callback;
ctx = node.context;
if ((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx);
}
}
}
return this;
},
// Trigger an event, firing all bound callbacks. Callbacks are passed the
// same arguments as `trigger` is, apart from the event name.
// Listening for `"all"` passes the true event name as the first argument.
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger: function(events) {
var event, node, calls, tail, args, all, rest;
if (!(calls = this._callbacks)) return this;
all = calls['all'];
(events = events.split(/\s+/)).push(null);
// Save references to the current heads & tails.
while (event = events.shift()) {
if (all) events.push({next: all.next, tail: all.tail, event: event});
if (!(node = calls[event])) continue;
events.push({next: node.next, tail: node.tail});
}
// Traverse each list, stopping when the saved tail is reached.
all = calls.all;
events = events.split(eventSplitter);
rest = slice.call(arguments, 1);
while (node = events.pop()) {
tail = node.tail;
args = node.event ? [node.event].concat(rest) : rest;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
// For each event, walk through the linked list of callbacks twice,
// first to trigger the event, then to trigger any `"all"` callbacks.
while (event = events.shift()) {
if (node = calls[event]) {
tail = node.tail;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, rest);
}
}
if (node = all) {
tail = node.tail;
args = [event].concat(rest);
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
}
}
}
return this;
}
};
// Aliases for backwards compatibility.
Backbone.Events.bind = Backbone.Events.on;
Backbone.Events.unbind = Backbone.Events.off;
Events.bind = Events.on;
Events.unbind = Events.off;
// Backbone.Model
// --------------
// Create a new model, with defined attributes. A client id (`cid`)
// is automatically generated and assigned for you.
Backbone.Model = function(attributes, options) {
var Model = Backbone.Model = function(attributes, options) {
var defaults;
attributes || (attributes = {});
if (options && options.parse) attributes = this.parse(attributes);
@ -173,16 +197,31 @@
this.attributes = {};
this._escapedAttributes = {};
this.cid = _.uniqueId('c');
if (!this.set(attributes, {silent: true})) {
throw new Error("Can't create an invalid model");
}
delete this._changed;
this.changed = {};
this._silent = {};
this._pending = {};
this.set(attributes, {silent: true});
// Reset change tracking.
this.changed = {};
this._silent = {};
this._pending = {};
this._previousAttributes = _.clone(this.attributes);
this.initialize.apply(this, arguments);
};
// Attach all inheritable methods to the Model prototype.
_.extend(Backbone.Model.prototype, Backbone.Events, {
_.extend(Model.prototype, Events, {
// A hash of attributes whose current and previous value differ.
changed: null,
// A hash of attributes that have silently changed since the last time
// `change` was called. Will become pending attributes on the next call.
_silent: null,
// A hash of attributes that have changed since the last `'change'` event
// began.
_pending: null,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
@ -193,7 +232,7 @@
initialize: function(){},
// Return a copy of the model's `attributes` object.
toJSON: function() {
toJSON: function(options) {
return _.clone(this.attributes);
},
@ -206,20 +245,22 @@
escape: function(attr) {
var html;
if (html = this._escapedAttributes[attr]) return html;
var val = this.attributes[attr];
var val = this.get(attr);
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has: function(attr) {
return this.attributes[attr] != null;
return this.get(attr) != null;
},
// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set: function(key, value, options) {
var attrs, attr, val;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
@ -231,7 +272,7 @@
// Extract attributes and options.
options || (options = {});
if (!attrs) return this;
if (attrs instanceof Backbone.Model) attrs = attrs.attributes;
if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0;
// Run validation.
@ -240,33 +281,37 @@
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {};
var now = this.attributes;
var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {};
var alreadySetting = this._setting;
this._changed || (this._changed = {});
this._setting = true;
// Update attributes.
// For each `set` attribute...
for (attr in attrs) {
val = attrs[attr];
if (!_.isEqual(now[attr], val)) delete escaped[attr];
options.unset ? delete now[attr] : now[attr] = val;
if (this._changing && !_.isEqual(this._changed[attr], val)) {
this.trigger('change:' + attr, this, val, options);
this._moreChanges = true;
// If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true;
}
delete this._changed[attr];
// Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val;
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
this._changed[attr] = val;
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
} else {
delete this.changed[attr];
delete this._pending[attr];
}
}
// Fire the `"change"` events, if the model has been changed.
if (!alreadySetting) {
if (!options.silent && this.hasChanged()) this.change(options);
this._setting = false;
}
// Fire the `"change"` events.
if (!options.silent) this.change(options);
return this;
},
@ -304,6 +349,8 @@
// state will be `set` again.
save: function(key, value, options) {
var attrs, current;
// Handle both `("key", value)` and `({key: value})` -style calls.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
@ -311,18 +358,30 @@
attrs = {};
attrs[key] = value;
}
options = options ? _.clone(options) : {};
if (options.wait) current = _.clone(this.attributes);
// If we're "wait"-ing to set changed attributes, validate early.
if (options.wait) {
if (!this._validate(attrs, options)) return false;
current = _.clone(this.attributes);
}
// Regular saves `set` attributes before persisting to the server.
var silentOptions = _.extend({}, options, {silent: true});
if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
return false;
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
var serverAttrs = model.parse(resp, xhr);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (options.wait) {
delete options.wait;
serverAttrs = _.extend(attrs || {}, serverAttrs);
}
if (!model.set(serverAttrs, options)) return false;
if (success) {
success(model, resp);
@ -330,6 +389,8 @@
model.trigger('sync', model, resp, options);
}
};
// Finish configuring and sending the Ajax request.
options.error = Backbone.wrapError(options.error, model, options);
var method = this.isNew() ? 'create' : 'update';
var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
@ -349,7 +410,11 @@
model.trigger('destroy', model, model.collection, options);
};
if (this.isNew()) return triggerDestroy();
if (this.isNew()) {
triggerDestroy();
return false;
}
options.success = function(resp) {
if (options.wait) triggerDestroy();
if (success) {
@ -358,6 +423,7 @@
model.trigger('sync', model, resp, options);
}
};
options.error = Backbone.wrapError(options.error, model, options);
var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
if (!options.wait) triggerDestroy();
@ -368,7 +434,7 @@
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url: function() {
var base = getValue(this.collection, 'url') || getValue(this, 'urlRoot') || urlError();
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
@ -393,18 +459,33 @@
// a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update.
change: function(options) {
if (this._changing || !this.hasChanged()) return this;
options || (options = {});
var changing = this._changing;
this._changing = true;
this._moreChanges = true;
for (var attr in this._changed) {
this.trigger('change:' + attr, this, this._changed[attr], options);
// Silent changes become pending changes.
for (var attr in this._silent) this._pending[attr] = true;
// Silent changes are triggered.
var changes = _.extend({}, options.changes, this._silent);
this._silent = {};
for (var attr in changes) {
this.trigger('change:' + attr, this, this.get(attr), options);
}
while (this._moreChanges) {
this._moreChanges = false;
if (changing) return this;
// Continue firing `"change"` events while there are pending changes.
while (!_.isEmpty(this._pending)) {
this._pending = {};
this.trigger('change', this, options);
// Pending and silent changes still remain.
for (var attr in this.changed) {
if (this._pending[attr] || this._silent[attr]) continue;
delete this.changed[attr];
}
this._previousAttributes = _.clone(this.attributes);
}
this._previousAttributes = _.clone(this.attributes);
delete this._changed;
this._changing = false;
return this;
},
@ -412,8 +493,8 @@
// Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged: function(attr) {
if (!arguments.length) return !_.isEmpty(this._changed);
return this._changed && _.has(this._changed, attr);
if (!arguments.length) return !_.isEmpty(this.changed);
return _.has(this.changed, attr);
},
// Return an object containing all the attributes that have changed, or
@ -423,7 +504,7 @@
// You can also pass an attributes object to diff against the model,
// determining if there *would be* a change.
changedAttributes: function(diff) {
if (!diff) return this.hasChanged() ? _.clone(this._changed) : false;
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
var val, changed = false, old = this._previousAttributes;
for (var attr in diff) {
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
@ -451,9 +532,9 @@
return !this.validate(this.attributes);
},
// Run validation against a set of incoming attributes, returning `true`
// if all is well. If a specific `error` callback has been passed,
// call that instead of firing the general `"error"` event.
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event.
_validate: function(attrs, options) {
if (options.silent || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
@ -475,8 +556,9 @@
// Provides a standard collection class for our sets of models, ordered
// or unordered. If a `comparator` is specified, the Collection will maintain
// its models in sort order, as they're added and removed.
Backbone.Collection = function(models, options) {
var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
@ -484,11 +566,11 @@
};
// Define the Collection's inheritable methods.
_.extend(Backbone.Collection.prototype, Backbone.Events, {
_.extend(Collection.prototype, Events, {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model: Backbone.Model,
model: Model,
// Initialize is an empty function by default. Override it with your own
// initialization logic.
@ -496,14 +578,14 @@
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON: function() {
return this.map(function(model){ return model.toJSON(); });
toJSON: function(options) {
return this.map(function(model){ return model.toJSON(options); });
},
// Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `add` event for every new model.
add: function(models, options) {
var i, index, length, model, cid, id, cids = {}, ids = {};
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
@ -513,16 +595,24 @@
if (!(model = models[i] = this._prepareModel(models[i], options))) {
throw new Error("Can't add an invalid model to a collection");
}
if (cids[cid = model.cid] || this._byCid[cid] ||
(((id = model.id) != null) && (ids[id] || this._byId[id]))) {
throw new Error("Can't add the same model to a collection twice");
cid = model.cid;
id = model.id;
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
dups.push(i);
continue;
}
cids[cid] = ids[id] = model;
}
// Remove duplicates.
i = dups.length;
while (i--) {
models.splice(dups[i], 1);
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
for (i = 0; i < length; i++) {
for (i = 0, length = models.length; i < length; i++) {
(model = models[i]).on('all', this._onModelEvent, this);
this._byCid[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
@ -566,9 +656,37 @@
return this;
},
// Add a model to the end of the collection.
push: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, options);
return model;
},
// Remove a model from the end of the collection.
pop: function(options) {
var model = this.at(this.length - 1);
this.remove(model, options);
return model;
},
// Add a model to the beginning of the collection.
unshift: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, _.extend({at: 0}, options));
return model;
},
// Remove a model from the beginning of the collection.
shift: function(options) {
var model = this.at(0);
this.remove(model, options);
return model;
},
// Get a model from the set by id.
get: function(id) {
if (id == null) return null;
if (id == null) return void 0;
return this._byId[id.id != null ? id.id : id];
},
@ -582,6 +700,17 @@
return this.models[index];
},
// Return models with matching attributes. Useful for simple cases of `filter`.
where: function(attrs) {
if (_.isEmpty(attrs)) return [];
return this.filter(function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
},
// Force the collection to re-sort itself. You don't need to call this under
// normal circumstances, as the set will maintain sort order as each item
// is added.
@ -613,7 +742,7 @@
this._removeReference(this.models[i]);
}
this._reset();
this.add(models, {silent: true, parse: options.parse});
this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
return this;
},
@ -679,7 +808,8 @@
// Prepare a model or hash of attributes to be added to this collection.
_prepareModel: function(model, options) {
if (!(model instanceof Backbone.Model)) {
options || (options = {});
if (!(model instanceof Model)) {
var attrs = model;
options.collection = this;
model = new this.model(attrs, options);
@ -702,12 +832,12 @@
// Sets need to update their indexes when models change ids. All other
// events simply proxy through. "add" and "remove" events that originate
// in other collections are ignored.
_onModelEvent: function(ev, model, collection, options) {
if ((ev == 'add' || ev == 'remove') && collection != this) return;
if (ev == 'destroy') {
_onModelEvent: function(event, model, collection, options) {
if ((event == 'add' || event == 'remove') && collection != this) return;
if (event == 'destroy') {
this.remove(model, options);
}
if (model && ev === 'change:' + model.idAttribute) {
if (model && event === 'change:' + model.idAttribute) {
delete this._byId[model.previous(model.idAttribute)];
this._byId[model.id] = model;
}
@ -725,7 +855,7 @@
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
Backbone.Collection.prototype[method] = function() {
Collection.prototype[method] = function() {
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
};
});
@ -735,7 +865,7 @@
// Routers map faux-URLs to actions, and fire events when routes are
// matched. Creating a new one sets its `routes` hash, if not set statically.
Backbone.Router = function(options) {
var Router = Backbone.Router = function(options) {
options || (options = {});
if (options.routes) this.routes = options.routes;
this._bindRoutes();
@ -749,7 +879,7 @@
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Backbone.Router.prototype, Backbone.Events, {
_.extend(Router.prototype, Events, {
// Initialize is an empty function by default. Override it with your own
// initialization logic.
@ -762,7 +892,7 @@
// });
//
route: function(route, name, callback) {
Backbone.history || (Backbone.history = new Backbone.History);
Backbone.history || (Backbone.history = new History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment) {
@ -815,7 +945,7 @@
// Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling.
Backbone.History = function() {
var History = Backbone.History = function() {
this.handlers = [];
_.bindAll(this, 'checkUrl');
};
@ -827,15 +957,23 @@
var isExplorer = /msie [\w.]+/;
// Has the history handling already been started?
var historyStarted = false;
History.started = false;
// Set up all inheritable **Backbone.History** properties and methods.
_.extend(Backbone.History.prototype, Backbone.Events, {
_.extend(History.prototype, Events, {
// The default interval to poll for hash changes, if necessary, is
// twenty times a second.
interval: 50,
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash: function(windowOverride) {
var loc = windowOverride ? windowOverride.location : window.location;
var match = loc.href.match(/#(.*)$/);
return match ? match[1] : '';
},
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment: function(fragment, forcePushState) {
@ -845,10 +983,9 @@
var search = window.location.search;
if (search) fragment += search;
} else {
fragment = window.location.hash;
fragment = this.getHash();
}
}
fragment = decodeURIComponent(fragment);
if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
return fragment.replace(routeStripper, '');
},
@ -856,10 +993,11 @@
// Start the hash change handling, returning `true` if the current URL matches
// an existing route, and `false` otherwise.
start: function(options) {
if (History.started) throw new Error("Backbone.history has already been started");
History.started = true;
// Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available?
if (historyStarted) throw new Error("Backbone.history has already been started");
this.options = _.extend({}, {root: '/'}, this.options, options);
this._wantsHashChange = this.options.hashChange !== false;
this._wantsPushState = !!this.options.pushState;
@ -867,6 +1005,7 @@
var fragment = this.getFragment();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
if (oldIE) {
this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
@ -885,7 +1024,6 @@
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this.fragment = fragment;
historyStarted = true;
var loc = window.location;
var atRoot = loc.pathname == this.options.root;
@ -900,7 +1038,7 @@
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = loc.hash.replace(routeStripper, '');
this.fragment = this.getHash().replace(routeStripper, '');
window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
}
@ -914,7 +1052,7 @@
stop: function() {
$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
clearInterval(this._checkUrlInterval);
historyStarted = false;
History.started = false;
},
// Add a route to be tested when the fragment changes. Routes added later
@ -927,10 +1065,10 @@
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl: function(e) {
var current = this.getFragment();
if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
if (current == this.fragment) return false;
if (this.iframe) this.navigate(current);
this.loadUrl() || this.loadUrl(window.location.hash);
this.loadUrl() || this.loadUrl(this.getHash());
},
// Attempt to load the current URL fragment. If a route succeeds with a
@ -953,12 +1091,12 @@
//
// The options object can contain `trigger: true` if you wish to have the
// route callback be fired (not usually desirable), or `replace: true`, if
// you which to modify the current URL without adding an entry to the history.
// you wish to modify the current URL without adding an entry to the history.
navigate: function(fragment, options) {
if (!historyStarted) return false;
if (!History.started) return false;
if (!options || options === true) options = {trigger: options};
var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
if (this.fragment == frag) return;
// If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) {
@ -971,7 +1109,7 @@
} else if (this._wantsHashChange) {
this.fragment = frag;
this._updateHash(window.location, frag, options.replace);
if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
// When replace is true, we don't want this.
if(!options.replace) this.iframe.document.open().close();
@ -1002,7 +1140,7 @@
// Creating a Backbone.View creates its initial element outside of the DOM,
// if an existing element is not provided...
Backbone.View = function(options) {
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
this._ensureElement();
@ -1011,13 +1149,13 @@
};
// Cached regex to split keys for `delegate`.
var eventSplitter = /^(\S+)\s*(.*)$/;
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
// List of view options to be merged as properties.
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
// Set up all inheritable **Backbone.View** properties and methods.
_.extend(Backbone.View.prototype, Backbone.Events, {
_.extend(View.prototype, Events, {
// The default `tagName` of a View's element is `"div"`.
tagName: 'div',
@ -1061,7 +1199,8 @@
// Change the view's element (`this.el` property), including event
// re-delegation.
setElement: function(element, delegate) {
this.$el = $(element);
if (this.$el) this.undelegateEvents();
this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];
if (delegate !== false) this.delegateEvents();
return this;
@ -1088,8 +1227,8 @@
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) throw new Error('Event "' + events[key] + '" does not exist');
var match = key.match(eventSplitter);
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
var match = key.match(delegateEventSplitter);
var eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.cid;
@ -1145,8 +1284,7 @@
};
// Set up inheritance for the model, collection, and view.
Backbone.Model.extend = Backbone.Collection.extend =
Backbone.Router.extend = Backbone.View.extend = extend;
Model.extend = Collection.extend = Router.extend = View.extend = extend;
// Backbone.sync
// -------------
@ -1177,6 +1315,9 @@
Backbone.sync = function(method, model, options) {
var type = methodMap[method];
// Default options, unless specified.
options || (options = {});
// Default JSON-request options.
var params = {type: type, dataType: 'json'};

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -0,0 +1,24 @@
.cleditorMain {border:1px solid #999; padding:0 1px 1px; background-color:white}
.cleditorMain iframe {border:none; margin:0; padding:0}
.cleditorMain textarea {border:none; margin:0; padding:0; overflow-y:scroll; font:10pt Arial,Verdana; resize:none; outline:none /* webkit grip focus */}
.cleditorToolbar {background: url('images/toolbar.gif') repeat}
.cleditorGroup {float:left; height:26px}
.cleditorButton {float:left; width:24px; height:24px; margin:1px 0 1px 0; background: url('images/buttons.gif')}
.cleditorDisabled {opacity:0.3; filter:alpha(opacity=30)}
.cleditorDivider {float:left; width:1px; height:23px; margin:1px 0 1px 0; background:#CCC}
.cleditorPopup {border:solid 1px #999; background-color:white; position:absolute; font:10pt Arial,Verdana; cursor:default; z-index:10000}
.cleditorList div {padding:2px 4px 2px 4px}
.cleditorList p,
.cleditorList h1,
.cleditorList h2,
.cleditorList h3,
.cleditorList h4,
.cleditorList h5,
.cleditorList h6,
.cleditorList font {padding:0; margin:0; background-color:Transparent}
.cleditorColor {width:150px; padding:1px 0 0 1px}
.cleditorColor div {float:left; width:14px; height:14px; margin:0 1px 1px 0}
.cleditorPrompt {background-color:#F6F7F9; padding:4px; font-size:8.5pt}
.cleditorPrompt input,
.cleditorPrompt textarea {font:8.5pt Arial,Verdana;}
.cleditorMsg {background-color:#FDFCEE; width:150px; padding:4px; font-size:8.5pt}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
/*
CLEditor WYSIWYG HTML Editor v1.3.0
http://premiumsoftware.net/cleditor
requires jQuery v1.4.2 or later
Copyright 2010, Chris Landowski, Premium Software, LLC
Dual licensed under the MIT or GPL Version 2 licenses.
*/
(function(e){function aa(a){var b=this,c=a.target,d=e.data(c,x),h=s[d],f=h.popupName,i=p[f];if(!(b.disabled||e(c).attr(n)==n)){var g={editor:b,button:c,buttonName:d,popup:i,popupName:f,command:h.command,useCSS:b.options.useCSS};if(h.buttonClick&&h.buttonClick(a,g)===false)return false;if(d=="source"){if(t(b)){delete b.range;b.$area.hide();b.$frame.show();c.title=h.title}else{b.$frame.hide();b.$area.show();c.title="Show Rich Text"}setTimeout(function(){u(b)},100)}else if(!t(b))if(f){var j=e(i);if(f==
"url"){if(d=="link"&&M(b)===""){z(b,"A selection is required when inserting a link.",c);return false}j.children(":button").unbind(q).bind(q,function(){var k=j.find(":text"),o=e.trim(k.val());o!==""&&v(b,g.command,o,null,g.button);k.val("http://");r();w(b)})}else f=="pastetext"&&j.children(":button").unbind(q).bind(q,function(){var k=j.find("textarea"),o=k.val().replace(/\n/g,"<br />");o!==""&&v(b,g.command,o,null,g.button);k.val("");r();w(b)});if(c!==e.data(i,A)){N(b,i,c);return false}return}else if(d==
"print")b.$frame[0].contentWindow.print();else if(!v(b,g.command,g.value,g.useCSS,c))return false;w(b)}}function O(a){a=e(a.target).closest("div");a.css(H,a.data(x)?"#FFF":"#FFC")}function P(a){e(a.target).closest("div").css(H,"transparent")}function ba(a){var b=a.data.popup,c=a.target;if(!(b===p.msg||e(b).hasClass(B))){var d=e.data(b,A),h=e.data(d,x),f=s[h],i=f.command,g,j=this.options.useCSS;if(h=="font")g=c.style.fontFamily.replace(/"/g,"");else if(h=="size"){if(c.tagName=="DIV")c=c.children[0];
g=c.innerHTML}else if(h=="style")g="<"+c.tagName+">";else if(h=="color")g=Q(c.style.backgroundColor);else if(h=="highlight"){g=Q(c.style.backgroundColor);if(l)i="backcolor";else j=true}b={editor:this,button:d,buttonName:h,popup:b,popupName:f.popupName,command:i,value:g,useCSS:j};if(!(f.popupClick&&f.popupClick(a,b)===false)){if(b.command&&!v(this,b.command,b.value,b.useCSS,d))return false;r();w(this)}}}function C(a){for(var b=1,c=0,d=0;d<a.length;++d){b=(b+a.charCodeAt(d))%65521;c=(c+b)%65521}return c<<
16|b}function R(a,b,c,d,h){if(p[a])return p[a];var f=e(m).hide().addClass(ca).appendTo("body");if(d)f.html(d);else if(a=="color"){b=b.colors.split(" ");b.length<10&&f.width("auto");e.each(b,function(i,g){e(m).appendTo(f).css(H,"#"+g)});c=da}else if(a=="font")e.each(b.fonts.split(","),function(i,g){e(m).appendTo(f).css("fontFamily",g).html(g)});else if(a=="size")e.each(b.sizes.split(","),function(i,g){e(m).appendTo(f).html("<font size="+g+">"+g+"</font>")});else if(a=="style")e.each(b.styles,function(i,
g){e(m).appendTo(f).html(g[1]+g[0]+g[1].replace("<","</"))});else if(a=="url"){f.html('Enter URL:<br><input type=text value="http://" size=35><br><input type=button value="Submit">');c=B}else if(a=="pastetext"){f.html("Paste your content here and click submit.<br /><textarea cols=40 rows=3></textarea><br /><input type=button value=Submit>");c=B}if(!c&&!d)c=S;f.addClass(c);l&&f.attr(I,"on").find("div,font,p,h1,h2,h3,h4,h5,h6").attr(I,"on");if(f.hasClass(S)||h===true)f.children().hover(O,P);p[a]=f[0];
return f[0]}function T(a,b){if(b){a.$area.attr(n,n);a.disabled=true}else{a.$area.removeAttr(n);delete a.disabled}try{if(l)a.doc.body.contentEditable=!b;else a.doc.designMode=!b?"on":"off"}catch(c){}u(a)}function v(a,b,c,d,h){D(a);if(!l){if(d===undefined||d===null)d=a.options.useCSS;a.doc.execCommand("styleWithCSS",0,d.toString())}d=true;var f;if(l&&b.toLowerCase()=="inserthtml")y(a).pasteHTML(c);else{try{d=a.doc.execCommand(b,0,c||null)}catch(i){f=i.description;d=false}d||("cutcopypaste".indexOf(b)>
-1?z(a,"For security reasons, your browser does not support the "+b+" command. Try using the keyboard shortcut or context menu instead.",h):z(a,f?f:"Error executing the "+b+" command.",h))}u(a);return d}function w(a){setTimeout(function(){t(a)?a.$area.focus():a.$frame[0].contentWindow.focus();u(a)},0)}function y(a){if(l)return J(a).createRange();return J(a).getRangeAt(0)}function J(a){if(l)return a.doc.selection;return a.$frame[0].contentWindow.getSelection()}function Q(a){var b=/rgba?\((\d+), (\d+), (\d+)/.exec(a),
c=a.split("");if(b)for(a=(b[1]<<16|b[2]<<8|b[3]).toString(16);a.length<6;)a="0"+a;return"#"+(a.length==6?a:c[1]+c[1]+c[2]+c[2]+c[3]+c[3])}function r(){e.each(p,function(a,b){e(b).hide().unbind(q).removeData(A)})}function U(){var a=e("link[href$='jquery.cleditor.css']").attr("href");return a.substr(0,a.length-19)+"images/"}function K(a){var b=a.$main,c=a.options;a.$frame&&a.$frame.remove();var d=a.$frame=e('<iframe frameborder="0" src="javascript:true;">').hide().appendTo(b),h=d[0].contentWindow,f=
a.doc=h.document,i=e(f);f.open();f.write(c.docType+"<html>"+(c.docCSSFile===""?"":'<head><link rel="stylesheet" type="text/css" href="'+c.docCSSFile+'" /></head>')+'<body style="'+c.bodyStyle+'"></body></html>');f.close();l&&i.click(function(){w(a)});E(a);if(l){i.bind("beforedeactivate beforeactivate selectionchange keypress",function(g){if(g.type=="beforedeactivate")a.inactive=true;else if(g.type=="beforeactivate"){!a.inactive&&a.range&&a.range.length>1&&a.range.shift();delete a.inactive}else if(!a.inactive){if(!a.range)a.range=
[];for(a.range.unshift(y(a));a.range.length>2;)a.range.pop()}});d.focus(function(){D(a)})}(e.browser.mozilla?i:e(h)).blur(function(){V(a,true)});i.click(r).bind("keyup mouseup",function(){u(a)});L?a.$area.show():d.show();e(function(){var g=a.$toolbar,j=g.children("div:last"),k=b.width();j=j.offset().top+j.outerHeight()-g.offset().top+1;g.height(j);j=(/%/.test(""+c.height)?b.height():parseInt(c.height))-j;d.width(k).height(j);a.$area.width(k).height(ea?j-2:j);T(a,a.disabled);u(a)})}function u(a){if(!L&&
e.browser.webkit&&!a.focused){a.$frame[0].contentWindow.focus();window.focus();a.focused=true}var b=a.doc;if(l)b=y(a);var c=t(a);e.each(a.$toolbar.find("."+W),function(d,h){var f=e(h),i=e.cleditor.buttons[e.data(h,x)],g=i.command,j=true;if(a.disabled)j=false;else if(i.getEnabled){j=i.getEnabled({editor:a,button:h,buttonName:i.name,popup:p[i.popupName],popupName:i.popupName,command:i.command,useCSS:a.options.useCSS});if(j===undefined)j=true}else if((c||L)&&i.name!="source"||l&&(g=="undo"||g=="redo"))j=
false;else if(g&&g!="print"){if(l&&g=="hilitecolor")g="backcolor";if(!l||g!="inserthtml")try{j=b.queryCommandEnabled(g)}catch(k){j=false}}if(j){f.removeClass(X);f.removeAttr(n)}else{f.addClass(X);f.attr(n,n)}})}function D(a){l&&a.range&&a.range[0].select()}function M(a){D(a);if(l)return y(a).text;return J(a).toString()}function z(a,b,c){var d=R("msg",a.options,fa);d.innerHTML=b;N(a,d,c)}function N(a,b,c){var d,h,f=e(b);if(c){var i=e(c);d=i.offset();h=--d.left;d=d.top+i.height()}else{i=a.$toolbar;
d=i.offset();h=Math.floor((i.width()-f.width())/2)+d.left;d=d.top+i.height()-2}r();f.css({left:h,top:d}).show();if(c){e.data(b,A,c);f.bind(q,{popup:b},e.proxy(ba,a))}setTimeout(function(){f.find(":text,textarea").eq(0).focus().select()},100)}function t(a){return a.$area.is(":visible")}function E(a,b){var c=a.$area.val(),d=a.options,h=d.updateFrame,f=e(a.doc.body);if(h){var i=C(c);if(b&&a.areaChecksum==i)return;a.areaChecksum=i}c=h?h(c):c;c=c.replace(/<(?=\/?script)/ig,"&lt;");if(d.updateTextArea)a.frameChecksum=
C(c);if(c!=f.html()){f.html(c);e(a).triggerHandler(F)}}function V(a,b){var c=e(a.doc.body).html(),d=a.options,h=d.updateTextArea,f=a.$area;if(h){var i=C(c);if(b&&a.frameChecksum==i)return;a.frameChecksum=i}c=h?h(c):c;if(d.updateFrame)a.areaChecksum=C(c);if(c!=f.val()){f.val(c);e(a).triggerHandler(F)}}e.cleditor={defaultOptions:{width:500,height:250,controls:"bold italic underline strikethrough subscript superscript | font size style | color highlight removeformat | bullets numbering | outdent indent | alignleft center alignright justify | undo redo | rule image link unlink | cut copy paste pastetext | print source",
colors:"FFF FCC FC9 FF9 FFC 9F9 9FF CFF CCF FCF CCC F66 F96 FF6 FF3 6F9 3FF 6FF 99F F9F BBB F00 F90 FC6 FF0 3F3 6CC 3CF 66C C6C 999 C00 F60 FC3 FC0 3C0 0CC 36F 63F C3C 666 900 C60 C93 990 090 399 33F 60C 939 333 600 930 963 660 060 366 009 339 636 000 300 630 633 330 030 033 006 309 303",fonts:"Arial,Arial Black,Comic Sans MS,Courier New,Narrow,Garamond,Georgia,Impact,Sans Serif,Serif,Tahoma,Trebuchet MS,Verdana",sizes:"1,2,3,4,5,6,7",styles:[["Paragraph","<p>"],["Header 1","<h1>"],["Header 2","<h2>"],
["Header 3","<h3>"],["Header 4","<h4>"],["Header 5","<h5>"],["Header 6","<h6>"]],useCSS:false,docType:'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',docCSSFile:"",bodyStyle:"margin:4px; font:10pt Arial,Verdana; cursor:text"},buttons:{init:"bold,,|italic,,|underline,,|strikethrough,,|subscript,,|superscript,,|font,,fontname,|size,Font Size,fontsize,|style,,formatblock,|color,Font Color,forecolor,|highlight,Text Highlight Color,hilitecolor,color|removeformat,Remove Formatting,|bullets,,insertunorderedlist|numbering,,insertorderedlist|outdent,,|indent,,|alignleft,Align Text Left,justifyleft|center,,justifycenter|alignright,Align Text Right,justifyright|justify,,justifyfull|undo,,|redo,,|rule,Insert Horizontal Rule,inserthorizontalrule|image,Insert Image,insertimage,url|link,Insert Hyperlink,createlink,url|unlink,Remove Hyperlink,|cut,,|copy,,|paste,,|pastetext,Paste as Text,inserthtml,|print,,|source,Show Source"},
imagesPath:function(){return U()}};e.fn.cleditor=function(a){var b=e([]);this.each(function(c,d){if(d.tagName=="TEXTAREA"){var h=e.data(d,Y);h||(h=new cleditor(d,a));b=b.add(h)}});return b};var H="backgroundColor",A="button",x="buttonName",F="change",Y="cleditor",q="click",n="disabled",m="<div>",I="unselectable",W="cleditorButton",X="cleditorDisabled",ca="cleditorPopup",S="cleditorList",da="cleditorColor",B="cleditorPrompt",fa="cleditorMsg",l=e.browser.msie,ea=/msie\s6/i.test(navigator.userAgent),
L=/iphone|ipad|ipod/i.test(navigator.userAgent),p={},Z,s=e.cleditor.buttons;e.each(s.init.split("|"),function(a,b){var c=b.split(","),d=c[0];s[d]={stripIndex:a,name:d,title:c[1]===""?d.charAt(0).toUpperCase()+d.substr(1):c[1],command:c[2]===""?d:c[2],popupName:c[3]===""?d:c[3]}});delete s.init;cleditor=function(a,b){var c=this;c.options=b=e.extend({},e.cleditor.defaultOptions,b);var d=c.$area=e(a).hide().data(Y,c).blur(function(){E(c,true)}),h=c.$main=e(m).addClass("cleditorMain").width(b.width).height(b.height),
f=c.$toolbar=e(m).addClass("cleditorToolbar").appendTo(h),i=e(m).addClass("cleditorGroup").appendTo(f);e.each(b.controls.split(" "),function(g,j){if(j==="")return true;if(j=="|"){e(m).addClass("cleditorDivider").appendTo(i);i=e(m).addClass("cleditorGroup").appendTo(f)}else{var k=s[j],o=e(m).data(x,k.name).addClass(W).attr("title",k.title).bind(q,e.proxy(aa,c)).appendTo(i).hover(O,P),G={};if(k.css)G=k.css;else if(k.image)G.backgroundImage="url("+U()+k.image+")";if(k.stripIndex)G.backgroundPosition=
k.stripIndex*-24;o.css(G);l&&o.attr(I,"on");k.popupName&&R(k.popupName,b,k.popupClass,k.popupContent,k.popupHover)}});h.insertBefore(d).append(d);if(!Z){e(document).click(function(g){g=e(g.target);g.add(g.parents()).is("."+B)||r()});Z=true}/auto|%/.test(""+b.width+b.height)&&e(window).resize(function(){K(c)});K(c)};var $=cleditor.prototype;e.each([["clear",function(a){a.$area.val("");E(a)}],["disable",T],["execCommand",v],["focus",w],["hidePopups",r],["sourceMode",t,true],["refresh",K],["select",
function(a){setTimeout(function(){t(a)?a.$area.select():v(a,"selectall")},0)}],["selectedHTML",function(a){D(a);a=y(a);if(l)return a.htmlText;var b=e("<layer>")[0];b.appendChild(a.cloneContents());return b.innerHTML},true],["selectedText",M,true],["showMessage",z],["updateFrame",E],["updateTextArea",V]],function(a,b){$[b[0]]=function(){for(var c=[this],d=0;d<arguments.length;d++)c.push(arguments[d]);c=b[1].apply(this,c);if(b[2])return c;return this}});$.change=function(a){var b=e(this);return a?b.bind(F,
a):b.trigger(F)}})(jQuery);

View File

@ -27,7 +27,7 @@
var $tip = this.tip();
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
$tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
$tip[0].className = 'tipsy openerp oe_tooltip '; // reset classname in case of dynamic gravity
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
var pos = $.extend({}, this.$element.offset(), {
@ -63,7 +63,7 @@
}
}
$tip.css(tp).addClass('openerp oe_tooltip tipsy-' + gravity);
$tip.css(tp).addClass('tipsy-' + gravity);
$tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
if (this.options.className) {
$tip.addClass(maybeCall(this.options.className, this.$element[0]));

View File

@ -0,0 +1,306 @@
//fgnass.github.com/spin.js#v1.2.5
(function(window, document, undefined) {
/**
* Copyright (c) 2011 Felix Gnass [fgnass at neteye dot de]
* Licensed under the MIT license
*/
var prefixes = ['webkit', 'Moz', 'ms', 'O']; /* Vendor prefixes */
var animations = {}; /* Animation rules keyed by their name */
var useCssAnimations;
/**
* Utility function to create elements. If no tag name is given,
* a DIV is created. Optionally properties can be passed.
*/
function createEl(tag, prop) {
var el = document.createElement(tag || 'div');
var n;
for(n in prop) {
el[n] = prop[n];
}
return el;
}
/**
* Appends children and returns the parent.
*/
function ins(parent /* child1, child2, ...*/) {
for (var i=1, n=arguments.length; i<n; i++) {
parent.appendChild(arguments[i]);
}
return parent;
}
/**
* Insert a new stylesheet to hold the @keyframe or VML rules.
*/
var sheet = function() {
var el = createEl('style');
ins(document.getElementsByTagName('head')[0], el);
return el.sheet || el.styleSheet;
}();
/**
* Creates an opacity keyframe animation rule and returns its name.
* Since most mobile Webkits have timing issues with animation-delay,
* we create separate rules for each line/segment.
*/
function addAnimation(alpha, trail, i, lines) {
var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-');
var start = 0.01 + i/lines*100;
var z = Math.max(1-(1-alpha)/trail*(100-start) , alpha);
var prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase();
var pre = prefix && '-'+prefix+'-' || '';
if (!animations[name]) {
sheet.insertRule(
'@' + pre + 'keyframes ' + name + '{' +
'0%{opacity:'+z+'}' +
start + '%{opacity:'+ alpha + '}' +
(start+0.01) + '%{opacity:1}' +
(start+trail)%100 + '%{opacity:'+ alpha + '}' +
'100%{opacity:'+ z + '}' +
'}', 0);
animations[name] = 1;
}
return name;
}
/**
* Tries various vendor prefixes and returns the first supported property.
**/
function vendor(el, prop) {
var s = el.style;
var pp;
var i;
if(s[prop] !== undefined) return prop;
prop = prop.charAt(0).toUpperCase() + prop.slice(1);
for(i=0; i<prefixes.length; i++) {
pp = prefixes[i]+prop;
if(s[pp] !== undefined) return pp;
}
}
/**
* Sets multiple style properties at once.
*/
function css(el, prop) {
for (var n in prop) {
el.style[vendor(el, n)||n] = prop[n];
}
return el;
}
/**
* Fills in default values.
*/
function merge(obj) {
for (var i=1; i < arguments.length; i++) {
var def = arguments[i];
for (var n in def) {
if (obj[n] === undefined) obj[n] = def[n];
}
}
return obj;
}
/**
* Returns the absolute page-offset of the given element.
*/
function pos(el) {
var o = {x:el.offsetLeft, y:el.offsetTop};
while((el = el.offsetParent)) {
o.x+=el.offsetLeft;
o.y+=el.offsetTop;
}
return o;
}
var defaults = {
lines: 12, // The number of lines to draw
length: 7, // The length of each line
width: 5, // The line thickness
radius: 10, // The radius of the inner circle
rotate: 0, // rotation offset
color: '#000', // #rgb or #rrggbb
speed: 1, // Rounds per second
trail: 100, // Afterglow percentage
opacity: 1/4, // Opacity of the lines
fps: 20, // Frames per second when using setTimeout()
zIndex: 2e9, // Use a high z-index by default
className: 'spinner', // CSS class to assign to the element
top: 'auto', // center vertically
left: 'auto' // center horizontally
};
/** The constructor */
var Spinner = function Spinner(o) {
if (!this.spin) return new Spinner(o);
this.opts = merge(o || {}, Spinner.defaults, defaults);
};
Spinner.defaults = {};
merge(Spinner.prototype, {
spin: function(target) {
this.stop();
var self = this;
var o = self.opts;
var el = self.el = css(createEl(0, {className: o.className}), {position: 'relative', zIndex: o.zIndex});
var mid = o.radius+o.length+o.width;
var ep; // element position
var tp; // target position
if (target) {
target.insertBefore(el, target.firstChild||null);
tp = pos(target);
ep = pos(el);
css(el, {
left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : o.left+mid) + 'px',
top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : o.top+mid) + 'px'
});
}
el.setAttribute('aria-role', 'progressbar');
self.lines(el, self.opts);
if (!useCssAnimations) {
// No CSS animation support, use setTimeout() instead
var i = 0;
var fps = o.fps;
var f = fps/o.speed;
var ostep = (1-o.opacity)/(f*o.trail / 100);
var astep = f/o.lines;
!function anim() {
i++;
for (var s=o.lines; s; s--) {
var alpha = Math.max(1-(i+s*astep)%f * ostep, o.opacity);
self.opacity(el, o.lines-s, alpha, o);
}
self.timeout = self.el && setTimeout(anim, ~~(1000/fps));
}();
}
return self;
},
stop: function() {
var el = this.el;
if (el) {
clearTimeout(this.timeout);
if (el.parentNode) el.parentNode.removeChild(el);
this.el = undefined;
}
return this;
},
lines: function(el, o) {
var i = 0;
var seg;
function fill(color, shadow) {
return css(createEl(), {
position: 'absolute',
width: (o.length+o.width) + 'px',
height: o.width + 'px',
background: color,
boxShadow: shadow,
transformOrigin: 'left',
transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
borderRadius: (o.width>>1) + 'px'
});
}
for (; i < o.lines; i++) {
seg = css(createEl(), {
position: 'absolute',
top: 1+~(o.width/2) + 'px',
transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
opacity: o.opacity,
animation: useCssAnimations && addAnimation(o.opacity, o.trail, i, o.lines) + ' ' + 1/o.speed + 's linear infinite'
});
if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}));
ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)')));
}
return el;
},
opacity: function(el, i, val) {
if (i < el.childNodes.length) el.childNodes[i].style.opacity = val;
}
});
/////////////////////////////////////////////////////////////////////////
// VML rendering for IE
/////////////////////////////////////////////////////////////////////////
/**
* Check and init VML support
*/
!function() {
function vml(tag, attr) {
return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr);
}
var s = css(createEl('group'), {behavior: 'url(#default#VML)'});
if (!vendor(s, 'transform') && s.adj) {
// VML support detected. Insert CSS rule ...
sheet.addRule('.spin-vml', 'behavior:url(#default#VML)');
Spinner.prototype.lines = function(el, o) {
var r = o.length+o.width;
var s = 2*r;
function grp() {
return css(vml('group', {coordsize: s +' '+s, coordorigin: -r +' '+-r}), {width: s, height: s});
}
var margin = -(o.width+o.length)*2+'px';
var g = css(grp(), {position: 'absolute', top: margin, left: margin});
var i;
function seg(i, dx, filter) {
ins(g,
ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
ins(css(vml('roundrect', {arcsize: 1}), {
width: r,
height: o.width,
left: o.radius,
top: -o.width>>1,
filter: filter
}),
vml('fill', {color: o.color, opacity: o.opacity}),
vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
)
)
);
}
if (o.shadow) {
for (i = 1; i <= o.lines; i++) {
seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)');
}
}
for (i = 1; i <= o.lines; i++) seg(i);
return ins(el, g);
};
Spinner.prototype.opacity = function(el, i, val, o) {
var c = el.firstChild;
o = o.shadow && o.lines || 0;
if (c && i+o < c.childNodes.length) {
c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild;
if (c) c.opacity = val;
}
};
}
else {
useCssAnimations = vendor(s, 'animation');
}
}();
window.Spinner = Spinner;
})(window, document);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -24,7 +24,7 @@
init: function(modules) {
// By default only web will be loaded, the rest will be by loaded
// by openerp.web.Session on the first session_authenticate
modules = modules || ["web"];
modules = _.union(['web'], modules || []);
var new_instance = {
// links to the global openerp
_openerp: openerp,
@ -37,7 +37,10 @@
};
openerp.instances[new_instance._session_id] = new_instance;
for(var i=0; i < modules.length; i++) {
openerp[modules[i]](new_instance,new_instance[modules[i]]);
new_instance[modules[i]] = {};
if (openerp[modules[i]]) {
openerp[modules[i]](new_instance,new_instance[modules[i]]);
}
}
return new_instance;
}
@ -48,7 +51,7 @@
* OpenERP Web web module split
*---------------------------------------------------------*/
openerp.web = function(session) {
var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import","view_editor"];
var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"];
for(var i=0; i<files.length; i++) {
if(openerp.web[files[i]]) {
openerp.web[files[i]](session);

File diff suppressed because it is too large Load Diff

View File

@ -378,7 +378,8 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
instance.web.EventDispatcherMixin.init.call(this);
this.__getterSetterInternalMap = {};
},
set: function(map) {
set: function(map, options) {
options = options || {};
var self = this;
var changed = false;
_.each(map, function(val, key) {
@ -387,10 +388,11 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
return;
changed = true;
self.__getterSetterInternalMap[key] = val;
self.trigger("change:" + key, self, {
oldValue: tmp,
newValue: val
});
if (! options.silent)
self.trigger("change:" + key, self, {
oldValue: tmp,
newValue: val
});
});
if (changed)
self.trigger("change", self);
@ -489,23 +491,19 @@ instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, {
*
* The semantics of this precisely replace closing over the method call.
*
* @param {String} method_name name of the method to invoke
* @param {String|Function} method function or name of the method to invoke
* @returns {Function} proxied method
*/
proxy: function (method_name) {
proxy: function (method) {
var self = this;
return function () {
return self[method_name].apply(self, arguments);
var fn = (typeof method === 'string') ? self[method] : method;
return fn.apply(self, arguments);
}
}
});
instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
/**
* Tag name when creating a default $element.
* @type string
*/
tagName: 'div',
/**
* Constructs the widget and sets its parent if a parent is given.
*
@ -515,14 +513,9 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
* When that widget is destroyed by calling destroy(), the current instance will be
* destroyed too. Can be null.
* @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
* to bind the current Widget to an already existing part of the DOM, which is not compatible
* with the DOM insertion methods provided by the current implementation of Widget. So
* for new components this argument should not be provided any more.
*/
init: function(parent) {
instance.web.CallbackEnabledMixin.init.call(this);
this.$element = $(document.createElement(this.tagName));
this.setParent(parent);
},
/**
@ -532,8 +525,8 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
_.each(this.getChildren(), function(el) {
el.destroy();
});
if(this.$element != null) {
this.$element.remove();
if(this.$el) {
this.$el.remove();
}
instance.web.PropertiesMixin.destroy.call(this);
},
@ -545,7 +538,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
appendTo: function(target) {
var self = this;
return this.__widgetRenderAndInsert(function(t) {
self.$element.appendTo(t);
self.$el.appendTo(t);
}, target);
},
/**
@ -556,7 +549,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
prependTo: function(target) {
var self = this;
return this.__widgetRenderAndInsert(function(t) {
self.$element.prependTo(t);
self.$el.prependTo(t);
}, target);
},
/**
@ -567,7 +560,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
insertAfter: function(target) {
var self = this;
return this.__widgetRenderAndInsert(function(t) {
self.$element.insertAfter(t);
self.$el.insertAfter(t);
}, target);
},
/**
@ -578,7 +571,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
insertBefore: function(target) {
var self = this;
return this.__widgetRenderAndInsert(function(t) {
self.$element.insertBefore(t);
self.$el.insertBefore(t);
}, target);
},
/**
@ -588,7 +581,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
*/
replace: function(target) {
return this.__widgetRenderAndInsert(_.bind(function(t) {
this.$element.replaceAll(t);
this.$el.replaceAll(t);
}, this), target);
},
__widgetRenderAndInsert: function(insertion, target) {
@ -611,6 +604,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
* @returns {jQuery.Deferred}
*/
start: function() {
return $.when();
}
});
@ -646,8 +640,8 @@ instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEn
* // stuff that you want to init before the rendering
* },
* start: function() {
* // 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 * /);
* // stuff you want to make after the rendering, `this.$el` holds a correct value
* this.$el.find(".my_button").click(/* an example of event binding * /);
*
* // if you have some asynchronous operations, it's a good idea to return
* // a promise in start()
@ -671,6 +665,12 @@ instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEn
* That will kill the widget in a clean way and erase its content from the dom.
*/
instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
// Backbone-ish API
tagName: 'div',
id: null,
className: null,
attributes: {},
events: {},
/**
* The name of the QWeb template that will be used for rendering. Must be
* redefined in subclasses or the default render() method can not be used.
@ -687,14 +687,12 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
* When that widget is destroyed by calling destroy(), the current instance will be
* destroyed too. Can be null.
* @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
* to bind the current Widget to an already existing part of the DOM, which is not compatible
* with the DOM insertion methods provided by the current implementation of Widget. So
* for new components this argument should not be provided any more.
*/
init: function(parent) {
instance.web.WidgetMixin.init.call(this,parent);
this.session = instance.connection;
// FIXME: this should not be
this.setElement(this._make_descriptive());
this.session = instance.session;
},
/**
* Renders the element. The default implementation renders the widget using QWeb,
@ -702,20 +700,120 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
* key that references `this`.
*/
renderElement: function() {
var rendered = null;
if (this.template)
rendered = instance.web.qweb.render(this.template, {widget: this});
if (_.str.trim(rendered)) {
var elem = $(rendered);
this.$element.replaceWith(elem);
this.$element = elem;
var $el;
if (this.template) {
$el = $(_.str.trim(instance.web.qweb.render(
this.template, {widget: this})));
} else {
$el = this._make_descriptive();
}
this.replaceElement($el);
},
/**
* Re-sets the widget's root element and replaces the old root element
* (if any) by the new one in the DOM.
*
* @param {HTMLElement | jQuery} $el
* @returns {*} this
*/
replaceElement: function ($el) {
var $oldel = this.$el;
this.setElement($el);
if ($oldel && !$oldel.is(this.$el)) {
$oldel.replaceWith(this.$el);
}
return this;
},
/**
* Shortcut for $element.find() like backbone
* Re-sets the widget's root element (el/$el/$el).
*
* Includes:
* * re-delegating events
* * re-binding sub-elements
* * if the widget already had a root element, replacing the pre-existing
* element in the DOM
*
* @param {HTMLElement | jQuery} element new root element for the widget
* @return {*} this
*/
"$": function() {
return this.$element.find.apply(this.$element,arguments);
setElement: function (element) {
// NB: completely useless, as WidgetMixin#init creates a $el
// always
if (this.$el) {
this.undelegateEvents();
}
this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];
this.delegateEvents();
return this;
},
/**
* Utility function to build small DOM elements.
*
* @param {String} tagName name of the DOM element to create
* @param {Object} [attributes] map of DOM attributes to set on the element
* @param {String} [content] HTML content to set on the element
* @return {Element}
*/
make: function (tagName, attributes, content) {
var el = document.createElement(tagName);
if (!_.isEmpty(attributes)) {
$(el).attr(attributes);
}
if (content) {
$(el).html(content);
}
return el;
},
/**
* Makes a potential root element from the declarative builder of the
* widget
*
* @return {jQuery}
* @private
*/
_make_descriptive: function () {
var attrs = _.extend({}, this.attributes || {});
if (this.id) { attrs.id = this.id; }
if (this.className) { attrs['class'] = this.className; }
return $(this.make(this.tagName, attrs));
},
delegateEvents: function () {
var events = this.events;
if (_.isEmpty(events)) { return; }
for(var key in events) {
if (!events.hasOwnProperty(key)) { continue; }
var method = this.proxy(events[key]);
var match = /^(\S+)(\s+(.*))?$/.exec(key);
var event = match[1];
var selector = match[3];
event += '.widget_events';
if (!selector) {
this.$el.on(event, method);
} else {
this.$el.on(event, selector, method);
}
}
},
undelegateEvents: function () {
this.$el.off('.widget_events');
},
/**
* Shortcut for ``this.$el.find(selector)``
*
* @param {String} selector CSS selector, rooted in $el
* @returns {jQuery} selector match
*/
$: function(selector) {
return this.$el.find(selector);
},
/**
* Informs the action manager to do an action. This supposes that
@ -743,7 +841,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
rpc: function(url, data, success, error) {
var def = $.Deferred().then(success, error);
var self = this;
instance.connection.rpc(url, data). then(function() {
instance.session.rpc(url, data). then(function() {
if (!self.isDestroyed())
def.resolve.apply(def, arguments);
}, function() {
@ -765,7 +863,7 @@ instance.web.Registry = instance.web.Class.extend({
*
* An object path is simply a dotted name from the instance root to the
* object pointed to (e.g. ``"instance.web.Session"`` for an OpenERP
* connection object).
* session object).
*
* @constructs instance.web.Registry
* @param {Object} mapping a mapping of keys to object-paths
@ -1037,7 +1135,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
uid: new py.float(this.uid),
datetime: datetime,
time: time,
relativedelta: relativedelta
relativedelta: relativedelta,
current_date: date.today.__call__().strftime(['%Y-%m-%d'])
};
},
/**
@ -1278,7 +1377,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
processData: false
}, url);
if (this.synch)
ajax.async = false;
ajax.async = false;
return $.ajax(ajax);
},
rpc_jsonp: function(url, payload) {
@ -1297,7 +1396,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
data: data
}, url);
if (this.synch)
ajax.async = false;
ajax.async = false;
var payload_str = JSON.stringify(payload);
var payload_url = $.param({r:payload_str});
if(payload_url.length < 2000) {

View File

@ -11,33 +11,6 @@ if (!console.debug) {
openerp.web.coresetup = function(instance) {
/**
* @deprecated use :class:`instance.web.Widget`
*/
instance.web.OldWidget = instance.web.Widget.extend({
init: function(parent, element_id) {
this._super(parent);
this.element_id = element_id;
this.element_id = this.element_id || _.uniqueId('widget-');
var tmp = document.getElementById(this.element_id);
this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName));
},
renderElement: function() {
var rendered = this.render();
if (rendered) {
var elem = $(rendered);
this.$element.replaceWith(elem);
this.$element = elem;
}
return this;
},
render: function (additional) {
if (this.template)
return instance.web.qweb.render(this.template, _.extend({widget: this}, additional || {}));
return null;
}
});
/** Session openerp specific RPC class */
instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Session# */{
init: function() {
@ -62,7 +35,6 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
this.username = false;
this.user_context= {};
this.db = false;
this.openerp_entreprise = false;
this.module_list = instance._modules.slice();
this.module_loaded = {};
_(this.module_list).each(function (mod) {
@ -105,8 +77,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
db: result.db,
username: result.login,
uid: result.uid,
user_context: result.context,
openerp_entreprise: result.openerp_entreprise
user_context: result.context
});
});
},
@ -130,8 +101,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
db: result.db,
username: result.login,
uid: result.uid,
user_context: result.context,
openerp_entreprise: result.openerp_entreprise
user_context: result.context
});
if (!_volatile) {
self.set_cookie('session_id', self.session_id);
@ -394,13 +364,13 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
timer = setTimeout(waitLoop, CHECK_INTERVAL);
},
synchronized_mode: function(to_execute) {
var synch = this.synch;
this.synch = true;
try {
return to_execute();
} finally {
this.synch = synch;
}
var synch = this.synch;
this.synch = true;
try {
return to_execute();
} finally {
this.synch = synch;
}
}
});
@ -416,12 +386,12 @@ instance.web.Bus = instance.web.Class.extend(instance.web.EventDispatcherMixin,
// check gtk bindings
// http://unixpapa.com/js/key.html
_.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) {
$('html').on(evtype, self, function(ev) {
$('html').on(evtype, function(ev) {
self.trigger(evtype, ev);
});
});
_.each('resize,scroll'.split(','), function(evtype) {
$(window).on(evtype, self, function(ev) {
$(window).on(evtype, function(ev) {
self.trigger(evtype, ev);
});
});
@ -541,20 +511,14 @@ $.async_when = function() {
// special tweak for the web client
var old_async_when = $.async_when;
$.async_when = function() {
if (instance.connection.synch)
return $.when.apply(this, arguments);
else
return old_async_when.apply(this, arguments);
if (instance.session.synch)
return $.when.apply(this, arguments);
else
return old_async_when.apply(this, arguments);
};
/** Setup blockui */
if ($.blockUI) {
$.blockUI.defaults.baseZ = 1100;
$.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">';
}
/** Setup default session */
instance.connection = new instance.web.Session();
instance.session = new instance.web.Session();
/** Configure default qweb */
instance.web._t = new instance.web.TranslationDataBase().build_translation_function();
@ -573,8 +537,8 @@ instance.web._lt = function (s) {
return {toString: function () { return instance.web._t(s); }}
};
instance.web.qweb = new QWeb2.Engine();
instance.web.qweb.default_dict['__debug__'] = instance.connection.debug; // Which one ?
instance.web.qweb.debug = instance.connection.debug;
instance.web.qweb.default_dict['__debug__'] = instance.session.debug; // Which one ?
instance.web.qweb.debug = instance.session.debug;
instance.web.qweb.default_dict = {
'_' : _,
'_t' : instance.web._t
@ -631,42 +595,96 @@ var _t = instance.web._t;
_t('%d years ago');
}
instance.connection.on('module_loaded', this, function () {
instance.session.on('module_loaded', this, function () {
// provide timeago.js with our own translator method
$.timeago.settings.translator = instance.web._t;
});
/** Setup blockui */
if ($.blockUI) {
$.blockUI.defaults.baseZ = 1100;
$.blockUI.defaults.message = '<div class="oe_blockui_spin_container">';
$.blockUI.defaults.css.border = '0';
$.blockUI.defaults.css["background-color"] = '';
}
var messages_by_seconds = function() {
return [
[0, _t("Loading...")],
[30, _t("Still loading...")],
[60, _t("Still loading...<br />Please be patient.")],
[120, _t("Don't leave yet,<br />it's still loading...")],
[300, _t("You may not believe it,<br />but the application is actually loading...")],
[600, _t("You know, sometimes,<br />OpenERP can be a little bit slow,<br />because it's loading...")],
[3600, _t("Maybe you should consider pressing F5...")],
];
};
instance.web.Throbber = instance.web.Widget.extend({
template: "Throbber",
start: function() {
var opts = {
lines: 13, // The number of lines to draw
length: 7, // The length of each line
width: 4, // The line thickness
radius: 10, // The radius of the inner circle
rotate: 0, // The rotation offset
color: '#FFF', // #rgb or #rrggbb
speed: 1, // Rounds per second
trail: 60, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
className: 'spinner', // The CSS class to assign to the spinner
zIndex: 2e9, // The z-index (defaults to 2000000000)
top: 'auto', // Top position relative to parent in px
left: 'auto' // Left position relative to parent in px
};
this.spin = new Spinner(opts).spin(this.$el[0]);
this.start_time = new Date().getTime();
this.act_message();
},
act_message: function() {
var self = this;
setTimeout(function() {
if (self.isDestroyed())
return;
var seconds = (new Date().getTime() - self.start_time) / 1000;
var mes;
_.each(messages_by_seconds(), function(el) {
if (seconds >= el[0])
mes = el[1];
});
self.$(".oe_throbber_message").html(mes);
self.act_message();
}, 1000);
},
destroy: function() {
if (this.spin)
this.spin.stop();
this._super();
},
});
instance.web.Throbber.throbbers = [];
instance.web.blockUI = function() {
var tmp = $.blockUI.apply($, arguments);
var throbber = new instance.web.Throbber();
instance.web.Throbber.throbbers.push(throbber);
throbber.appendTo($(".oe_blockui_spin_container"));
return tmp;
}
instance.web.unblockUI = function() {
_.each(instance.web.Throbber.throbbers, function(el) {
el.destroy();
});
return $.unblockUI.apply($, arguments);
}
/**
* Registry for all the client actions key: tag value: widget
*/
instance.web.client_actions = new instance.web.Registry();
/**
* Client action to reload the whole interface.
* If params has an entry 'menu_id', it opens the given menu entry.
*/
instance.web.Reload = instance.web.Widget.extend({
init: function(parent, params) {
this._super(parent);
this.menu_id = (params && params.menu_id) || false;
},
start: function() {
var l = window.location;
var timestamp = new Date().getTime();
var search = "?ts=" + timestamp;
if (l.search) {
search = l.search + "&ts=" + timestamp;
}
var hash = l.hash;
if (this.menu_id) {
hash = "#menu_id=" + this.menu_id;
}
var url = l.protocol + "//" + l.host + l.pathname + search + hash;
window.location = url;
}
});
instance.web.client_actions.add("reload", "instance.web.Reload");
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:

View File

@ -58,7 +58,7 @@ instance.web.Query = instance.web.Class.extend({
},
_execute: function () {
var self = this;
return instance.connection.rpc('/web/dataset/search_read', {
return instance.session.rpc('/web/dataset/search_read', {
model: this._model.name,
fields: this._fields || false,
domain: this._model.domain(this._filter),
@ -233,7 +233,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{
kwargs = args;
args = [];
}
return instance.connection.rpc('/web/dataset/call_kw', {
return instance.session.rpc('/web/dataset/call_kw', {
model: this.name,
method: method,
args: args,
@ -256,7 +256,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{
* @param {String} signal signal to trigger on the workflow
*/
exec_workflow: function (id, signal) {
return instance.connection.rpc('/web/dataset/exec_workflow', {
return instance.session.rpc('/web/dataset/exec_workflow', {
model: this.name,
id: id,
signal: signal
@ -282,7 +282,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{
*/
context: function (context) {
return new instance.web.CompoundContext(
instance.connection.user_context, this._context, context || {});
instance.session.user_context, this._context, context || {});
},
/**
* Button action caller, needs to perform cleanup if an action is returned
@ -292,7 +292,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{
* FIXME: remove when evaluator integrated
*/
call_button: function (method, args) {
return instance.connection.rpc('/web/dataset/call_button', {
return instance.session.rpc('/web/dataset/call_button', {
model: this.name,
method: method,
domain_id: null,
@ -439,7 +439,7 @@ instance.web.data = {
})
};
instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web.DataGroup# */{
instance.web.DataGroup = instance.web.CallbackEnabled.extend( /** @lends openerp.web.DataGroup# */{
/**
* Management interface between views and grouped collections of OpenERP
* records.
@ -451,9 +451,9 @@ instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web.
* content of the current grouping level.
*
* @constructs instance.web.DataGroup
* @extends instance.web.OldWidget
* @extends instance.web.CallbackEnabled
*
* @param {instance.web.OldWidget} parent widget
* @param {instance.web.CallbackEnabled} parent widget
* @param {String} model name of the model managed by this DataGroup
* @param {Array} domain search domain for this DataGroup
* @param {Object} context context of the DataGroup's searches
@ -524,13 +524,13 @@ instance.web.StaticDataGroup = instance.web.GrouplessDataGroup.extend( /** @lend
}
});
instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.DataSet# */{
instance.web.DataSet = instance.web.CallbackEnabled.extend( /** @lends openerp.web.DataSet# */{
/**
* DateaManagement interface between views and the collection of selected
* OpenERP records (represents the view's state?)
*
* @constructs instance.web.DataSet
* @extends instance.web.OldWidget
* @extends instance.web.CallbackEnabled
*
* @param {String} model the OpenERP model this dataset will manage
*/
@ -706,7 +706,7 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da
* @returns {$.Deferred}
*/
call_and_eval: function (method, args, domain_index, context_index, callback, error_callback) {
return this.rpc('/web/dataset/call', {
return instance.session.rpc('/web/dataset/call', {
model: this.model,
method: method,
domain_id: domain_index == undefined ? null : domain_index,
@ -803,7 +803,23 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da
return this.ids.length;
},
alter_ids: function(n_ids) {
this.ids = n_ids;
this.ids = n_ids;
},
/**
* Resequence records.
*
* @param {Array} ids identifiers of the records to resequence
* @returns {$.Deferred}
*/
resequence: function (ids, options) {
options = options || {};
return instance.session.rpc('/web/dataset/resequence', {
model: this.model,
ids: ids,
context: this._model.context(options.context),
}).pipe(function (results) {
return results;
});
},
});
instance.web.DataSetStatic = instance.web.DataSet.extend({
@ -1042,7 +1058,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
self.cache.push({id: id, values: record});
} else {
// I assume cache value is prioritary
cached.values = _.defaults(_.clone(cached.values), record);
cached.values = _.defaults(_.clone(cached.values), record);
}
});
return_records();
@ -1067,7 +1083,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
return this._super(method, args, callback, error_callback);
},
alter_ids: function(n_ids) {
this._super(n_ids);
this._super(n_ids);
this.on_change();
},
});

View File

@ -21,31 +21,31 @@ instance.web.DataExport = instance.web.Dialog.extend({
],
close: function(event, ui){ self.close();}
});
self.$element.removeClass('ui-dialog-content ui-widget-content');
self.$element.find('#add_field').click(function() {
self.$el.removeClass('ui-dialog-content ui-widget-content');
self.$el.find('#add_field').click(function() {
if ($('#field-tree-structure tr.ui-selected')) {
var fld = self.$element.find('#field-tree-structure tr.ui-selected').find('a');
var fld = self.$el.find('#field-tree-structure tr.ui-selected').find('a');
for (var i = 0; i < fld.length; i++) {
var id = $(fld[i]).attr('id').split('-')[1];
var string = $(fld[i]).attr('string');
self.add_field(id, string);
}
self.$element.find('#field-tree-structure tr').removeClass('ui-selected');
self.$el.find('#field-tree-structure tr').removeClass('ui-selected');
}
});
self.$element.find('#remove_field').click(function() {
self.$element.find('#fields_list option:selected').remove();
self.$el.find('#remove_field').click(function() {
self.$el.find('#fields_list option:selected').remove();
});
self.$element.find('#remove_all_field').click(function() {
self.$element.find('#fields_list').empty();
self.$el.find('#remove_all_field').click(function() {
self.$el.find('#fields_list').empty();
});
this.$element.find('#export_new_list').click(this.on_show_save_list);
this.$el.find('#export_new_list').click(this.on_show_save_list);
var got_fields = new $.Deferred();
this.$element.find('#import_compat').change(function() {
self.$element.find('#fields_list').empty();
self.$element.find('#field-tree-structure').remove();
var import_comp = self.$element.find("#import_compat").val();
this.$el.find('#import_compat').change(function() {
self.$el.find('#fields_list').empty();
self.$el.find('#field-tree-structure').remove();
var import_comp = self.$el.find("#import_compat").val();
self.rpc("/web/export/get_fields", {
model: self.dataset.model,
import_compat: Boolean(import_comp)
@ -61,7 +61,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
this.show_exports_list());
},
do_setup_export_formats: function (formats) {
var $fmts = this.$element.find('#export_format');
var $fmts = this.$el.find('#export_format');
_(formats).each(function (format) {
var opt = new Option(format.label, format.tag);
if (format.error) {
@ -76,8 +76,8 @@ instance.web.DataExport = instance.web.Dialog.extend({
},
show_exports_list: function() {
var self = this;
if (self.$element.find('#saved_export_list').is(':hidden')) {
self.$element.find('#ExistsExportList').show();
if (self.$el.find('#saved_export_list').is(':hidden')) {
self.$el.find('#ExistsExportList').show();
return;
}
return this.exports.read_slice(['name'], {
@ -86,35 +86,35 @@ instance.web.DataExport = instance.web.Dialog.extend({
if (!export_list.length) {
return;
}
self.$element.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list}));
self.$element.find('#saved_export_list').change(function() {
self.$element.find('#fields_list option').remove();
var export_id = self.$element.find('#saved_export_list option:selected').val();
self.$el.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list}));
self.$el.find('#saved_export_list').change(function() {
self.$el.find('#fields_list option').remove();
var export_id = self.$el.find('#saved_export_list option:selected').val();
if (export_id) {
self.rpc('/web/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}, self.do_load_export_field);
}
});
self.$element.find('#delete_export_list').click(function() {
var select_exp = self.$element.find('#saved_export_list option:selected');
self.$el.find('#delete_export_list').click(function() {
var select_exp = self.$el.find('#saved_export_list option:selected');
if (select_exp.val()) {
self.exports.unlink([parseInt(select_exp.val(), 10)]);
select_exp.remove();
if (self.$element.find('#saved_export_list option').length <= 1) {
self.$element.find('#ExistsExportList').hide();
if (self.$el.find('#saved_export_list option').length <= 1) {
self.$el.find('#ExistsExportList').hide();
}
}
});
});
},
do_load_export_field: function(field_list) {
var export_node = this.$element.find("#fields_list");
var export_node = this.$el.find("#fields_list");
_(field_list).each(function (field) {
export_node.append(new Option(field.label, field.name));
});
},
on_show_save_list: function() {
var self = this;
var current_node = self.$element.find("#savenewlist");
var current_node = self.$el.find("#savenewlist");
if (!(current_node.find("label")).length) {
current_node.append(QWeb.render('ExportNewList'));
current_node.find("#add_export_list").click(function() {
@ -150,14 +150,14 @@ instance.web.DataExport = instance.web.Dialog.extend({
if (!export_list_id.result) {
return;
}
self.$element.find("#saved_export_list").append(
self.$el.find("#saved_export_list").append(
new Option(value, export_list_id.result));
if (self.$element.find("#saved_export_list").is(":hidden")) {
if (self.$el.find("#saved_export_list").is(":hidden")) {
self.show_exports_list();
}
});
this.on_show_save_list();
this.$element.find("#fields_list option").remove();
this.$el.find("#fields_list option").remove();
},
on_click: function(id, record) {
var self = this;
@ -173,7 +173,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
}
if (!record.loaded) {
var import_comp = self.$element.find("#import_compat").val();
var import_comp = self.$el.find("#import_compat").val();
self.rpc("/web/export/get_fields", {
model: model,
prefix: prefix,
@ -191,44 +191,44 @@ instance.web.DataExport = instance.web.Dialog.extend({
},
on_show_data: function(result, after) {
var self = this;
var imp_cmpt = Boolean(self.$element.find("#import_compat").val());
var imp_cmpt = Boolean(self.$el.find("#import_compat").val());
if (after) {
var current_tr = self.$element.find("tr[id='treerow-" + after + "']");
var current_tr = self.$el.find("tr[id='treerow-" + after + "']");
current_tr.addClass('open');
current_tr.find('img').attr('src','/web/static/src/img/collapse.gif');
current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result}));
} else {
self.$element.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result}));
self.$el.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result}));
}
_.each(result, function(record) {
self.records[record.id] = record.value;
if (record.required) {
var required_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
var required_fld = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
required_fld.addClass("oe_export_requiredfield");
}
self.$element.find("img[id='parentimg-" + record.id +"']").click(function() {
self.$el.find("img[id='parentimg-" + record.id +"']").click(function() {
self.on_click(this.id, record);
});
self.$element.find("tr[id='treerow-" + record.id + "']").click(function(e) {
self.$el.find("tr[id='treerow-" + record.id + "']").click(function(e) {
if (e.shiftKey) {
var frst_click, scnd_click = '';
if (self.row_index == 0) {
self.row_index = this.rowIndex;
frst_click = self.$element.find("tr[id^='treerow-']")[self.row_index-1];
frst_click = self.$el.find("tr[id^='treerow-']")[self.row_index-1];
$(frst_click).addClass("ui-selected");
} else {
if (this.rowIndex >=self.row_index) {
for (var i = (self.row_index-1); i < this.rowIndex; i++) {
scnd_click = self.$element.find("tr[id^='treerow-']")[i];
scnd_click = self.$el.find("tr[id^='treerow-']")[i];
if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
$(scnd_click).addClass("ui-selected");
}
}
} else {
for (var i = (self.row_index-1); i >= (this.rowIndex-1); i--) {
scnd_click = self.$element.find("tr[id^='treerow-']")[i];
scnd_click = self.$el.find("tr[id^='treerow-']")[i];
if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
$(scnd_click).addClass("ui-selected");
}
@ -238,10 +238,10 @@ instance.web.DataExport = instance.web.Dialog.extend({
}
self.row_index = this.rowIndex;
self.$element.find("tr[id='treerow-" + record.id + "']").keyup(function() {
self.$el.find("tr[id='treerow-" + record.id + "']").keyup(function() {
self.row_index = 0;
});
var o2m_selection = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
var o2m_selection = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
if ($(o2m_selection).hasClass("oe_export_readonlyfield")) {
return false;
}
@ -252,14 +252,14 @@ instance.web.DataExport = instance.web.Dialog.extend({
$(this).addClass('ui-selected').find('a').focus();
}
} else if (!e.shiftKey) {
self.$element.find("tr.ui-selected")
self.$el.find("tr.ui-selected")
.removeClass("ui-selected").find('a').blur();
$(this).addClass("ui-selected").find('a').focus();
}
return false;
});
self.$element.find("tr[id='treerow-" + record.id + "']").keydown(function(e) {
self.$el.find("tr[id='treerow-" + record.id + "']").keydown(function(e) {
var keyCode = e.keyCode || e.which;
var arrow = {left: 37, up: 38, right: 39, down: 40 };
switch (keyCode) {
@ -297,19 +297,19 @@ instance.web.DataExport = instance.web.Dialog.extend({
break;
}
});
self.$element.find("tr[id='treerow-" + record.id + "']").dblclick(function() {
var $o2m_selection = self.$element.find("tr[id^='treerow-" + record.id + "']").find('#tree-column');
self.$el.find("tr[id='treerow-" + record.id + "']").dblclick(function() {
var $o2m_selection = self.$el.find("tr[id^='treerow-" + record.id + "']").find('#tree-column');
if (!$o2m_selection.hasClass("oe_export_readonlyfield")) {
self.add_field(record.id, $(this).find("a").attr("string"));
}
});
});
self.$element.find('#fields_list').mouseover(function(event) {
self.$el.find('#fields_list').mouseover(function(event) {
if (event.relatedTarget) {
if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) {
var field_id = event.relatedTarget.attributes["id"]["value"];
if (field_id && field_id.split("-")[0] === 'export') {
if (!self.$element.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) {
if (!self.$el.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) {
self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]);
}
}
@ -319,7 +319,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
},
showcontent: function(id) {
// show & hide the contents
var $this = this.$element.find("tr[id='treerow-" + id + "']");
var $this = this.$el.find("tr[id='treerow-" + id + "']");
var is_open = $this.hasClass('open');
$this.toggleClass('open');
@ -329,7 +329,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
} else {
first_child.attr('src', '/web/static/src/img/collapse.gif');
}
var child_field = this.$element.find("tr[id^='treerow-" + id +"/']");
var child_field = this.$el.find("tr[id^='treerow-" + id +"/']");
var child_len = (id.split("/")).length + 1;
for (var i = 0; i < child_field.length; i++) {
var $child = $(child_field[i]);
@ -345,15 +345,15 @@ instance.web.DataExport = instance.web.Dialog.extend({
}
},
add_field: function(field_id, string) {
var field_list = this.$element.find('#fields_list');
if (this.$element.find("#fields_list option[value='" + field_id + "']")
&& !this.$element.find("#fields_list option[value='" + field_id + "']").length) {
var field_list = this.$el.find('#fields_list');
if (this.$el.find("#fields_list option[value='" + field_id + "']")
&& !this.$el.find("#fields_list option[value='" + field_id + "']").length) {
field_list.append(new Option(string, field_id));
}
},
get_fields: function() {
var export_field = [];
this.$element.find("#fields_list option").each(function() {
this.$el.find("#fields_list option").each(function() {
export_field.push($(this).val());
});
if (!export_field.length) {
@ -363,7 +363,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
},
on_click_export_data: function() {
var self = this;
var exported_fields = this.$element.find('#fields_list option').map(function () {
var exported_fields = this.$el.find('#fields_list option').map(function () {
// DOM property is textContent, but IE8 only knows innerText
return {name: self.records[this.value] || this.value,
label: this.textContent || this.innerText};
@ -375,8 +375,8 @@ instance.web.DataExport = instance.web.Dialog.extend({
}
exported_fields.unshift({name: 'id', label: 'External ID'});
var export_format = this.$element.find("#export_format").val();
$.blockUI();
var export_format = this.$el.find("#export_format").val();
instance.web.blockUI();
this.session.get_file({
url: '/web/export/' + export_format,
data: {data: JSON.stringify({
@ -385,13 +385,13 @@ instance.web.DataExport = instance.web.Dialog.extend({
ids: this.dataset.ids,
domain: this.dataset.domain,
import_compat: Boolean(
this.$element.find("#import_compat").val())
this.$el.find("#import_compat").val())
})},
complete: $.unblockUI
complete: instance.web.unblockUI
});
},
close: function() {
this.$element.remove();
this.$el.remove();
this._super();
}
});

View File

@ -75,9 +75,9 @@ instance.web.DataImport = instance.web.Dialog.extend({
}
});
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.delegate('fieldset legend', 'click', function() {
this.$el.find('#csvfile').change(this.on_autodetect_data);
this.$el.find('fieldset').change(this.on_autodetect_data);
this.$el.delegate('fieldset legend', 'click', function() {
$(this).parent().toggleClass('oe_closed');
});
this.ready.push(new instance.web.DataSet(this, this.model).call(
@ -156,19 +156,19 @@ instance.web.DataImport = instance.web.Dialog.extend({
});
},
toggle_import_button: function (newstate) {
instance.web.dialog(this.$element, 'widget')
instance.web.dialog(this.$el, 'widget')
.find('.oe_import_dialog_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(!this.$el.find('#csvfile').val()) { return; }
var lines_to_skip = parseInt(this.$el.find('#csv_skip').val(), 10);
var with_headers = this.$el.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) {
this.$el.find(".sel_fields").each(function(index, element) {
var val = element.value;
if (!val) {
return;
@ -177,7 +177,7 @@ instance.web.DataImport = instance.web.Dialog.extend({
fields.push(val);
});
jsonp(this.$element.find('#import_data'), {
jsonp(this.$el.find('#import_data'), {
url: '/web/import/import_data',
data: {
model: this.model,
@ -190,19 +190,19 @@ instance.web.DataImport = instance.web.Dialog.extend({
}, this.on_import_results);
},
on_autodetect_data: function() {
if(!this.$element.find('#csvfile').val()) { return; }
jsonp(this.$element.find('#import_data'), {
if(!this.$el.find('#csvfile').val()) { return; }
jsonp(this.$el.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");
this.$el.find('#result').empty();
var headers, result_node = this.$el.find("#result");
if (results['error']) {
result_node.append(QWeb.render('ImportView.error', {
'error': results['error']}));
this.$element.find('fieldset').removeClass('oe_closed');
this.$el.find('fieldset').removeClass('oe_closed');
return;
}
if (results['success']) {
@ -214,8 +214,8 @@ instance.web.DataImport = instance.web.Dialog.extend({
}
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');
var lines_to_skip = parseInt(this.$el.find('#csv_skip').val(), 10),
with_headers = this.$el.find('#file_has_headers').prop('checked');
headers = with_headers ? results.records[0] : null;
result_node.append(QWeb.render('ImportView.result', {
@ -224,17 +224,17 @@ instance.web.DataImport = instance.web.Dialog.extend({
: with_headers ? results.records.slice(1)
: results.records
}));
this.$element.find('fieldset').addClass('oe_closed');
this.$el.find('fieldset').addClass('oe_closed');
}
this.$element.find('form').removeClass('oe_import_no_result');
this.$el.find('form').removeClass('oe_import_no_result');
this.$element.delegate('.oe_m2o_drop_down_button', 'click', function () {
this.$el.delegate('.oe_m2o_drop_down_button', 'click', function () {
$(this).prev('input').focus();
});
var self = this;
this.ready.then(function () {
var $fields = self.$element.find('.sel_fields').bind('blur', function () {
var $fields = self.$el.find('.sel_fields').bind('blur', function () {
if (this.value && !_(self.all_fields).contains(this.value)) {
this.value = '';
}
@ -315,9 +315,9 @@ instance.web.DataImport = instance.web.Dialog.extend({
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) {
this.$el.find(".sel_fields").each(function(index, element) {
var value = element.value;
var $element = $(element).removeClass('duplicate_fld');
var $el = $(element).removeClass('duplicate_fld');
if (!value) { return; }
if (!(value in values)) {
@ -329,13 +329,13 @@ instance.web.DataImport = instance.web.Dialog.extend({
} else {
duplicates[value] = [same_valued_field, element];
}
$element.add(same_valued_field).addClass('duplicate_fld');
$el.add(same_valued_field).addClass('duplicate_fld');
}
});
return duplicates;
},
on_check_field_values: function () {
this.$element.find("#message, #msg").remove();
this.$el.find("#message, #msg").remove();
var required_valid = this.check_required();
@ -343,7 +343,7 @@ instance.web.DataImport = instance.web.Dialog.extend({
if (_.isEmpty(duplicates)) {
this.toggle_import_button(required_valid);
} else {
var $err = $('<div id="msg" style="color: red;">'+_t("Destination fields should only be selected once, some fields are selected more than once:")+'</div>').insertBefore(this.$element.find('#result'));
var $err = $('<div id="msg" style="color: red;">'+_t("Destination fields should only be selected once, some fields are selected more than once:")+'</div>').insertBefore(this.$el.find('#result'));
var $dupes = $('<dl>').appendTo($err);
_(duplicates).each(function(elements, value) {
$('<dt>').text(value).appendTo($dupes);
@ -372,7 +372,7 @@ instance.web.DataImport = instance.web.Dialog.extend({
return f.id;
};
var selected_fields = _(this.$element.find('.sel_fields').get()).chain()
var selected_fields = _(this.$el.find('.sel_fields').get()).chain()
.pluck('value')
.compact()
.map(resolve_field_id)
@ -380,13 +380,13 @@ instance.web.DataImport = instance.web.Dialog.extend({
var missing_fields = _.difference(this.required_fields, selected_fields);
if (missing_fields.length) {
this.$element.find("#result").before('<div id="message" style="color:red">' + _t("*Required Fields are not selected :") + missing_fields + '.</div>');
this.$el.find("#result").before('<div id="message" style="color:red">' + _t("*Required Fields are not selected :") + missing_fields + '.</div>');
return false;
}
return true;
},
destroy: function() {
this.$element.remove();
this.$el.remove();
this._super();
}
});

View File

@ -271,82 +271,4 @@ instance.web.auto_date_to_str = function(value, type) {
}
};
/**
* Formats a provided cell based on its field type. Most of the field types
* return a correctly formatted value, but some tags and fields are
* special-cased in their handling:
*
* * buttons will return an actual ``<button>`` tag with a bunch of error handling
*
* * boolean fields will return a checkbox input, potentially disabled
*
* * binary fields will return a link to download the binary data as a file
*
* @param {Object} row_data record whose values should be displayed in the cell
* @param {Object} column column descriptor
* @param {"button"|"field"} column.tag base control type
* @param {String} column.type widget type for a field control
* @param {String} [column.string] button label
* @param {String} [column.icon] button icon
* @param {Object} [options]
* @param {String} [options.value_if_empty=''] what to display if the field's value is ``false``
* @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ?
* @param {String} [options.model] current record's model
* @param {Number} [options.id] current record's id
*
*/
instance.web.format_cell = function (row_data, column, options) {
options = options || {};
var attrs = {};
if (options.process_modifiers !== false) {
attrs = column.modifiers_for(row_data);
}
if (attrs.invisible) { return ''; }
if (column.tag === 'button') {
return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' +
'<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' +
'</button>', {
title: column.string || '',
additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ?
'disabled="disabled" class="oe_list_button_disabled"' : '',
prefix: instance.connection.prefix,
icon: column.icon,
alt: column.string || ''
});
}
if (!row_data[column.id]) {
return options.value_if_empty === undefined ? '' : options.value_if_empty;
}
switch (column.widget || column.type) {
case "boolean":
return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
row_data[column.id].value ? 'checked="checked"' : '');
case "binary":
var text = _t("Download"),
download_url = _.str.sprintf('/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', instance.connection.session_id, options.model, column.id, options.id);
if (column.filename) {
download_url += '&filename_field=' + column.filename;
if (row_data[column.filename]) {
text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
row_data[column.filename].value, {type: 'char'}));
}
}
return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
text: text,
href: download_url,
size: row_data[column.id].value
});
case 'progressbar':
return _.template(
'<progress value="<%-value%>" max="100"><%-value%>%</progress>', {
value: _.str.sprintf("%.0f", row_data[column.id].value || 0)
});
}
return _.escape(instance.web.format_value(
row_data[column.id].value, column, options.value_if_empty));
}
};

View File

@ -126,21 +126,21 @@ my.InputView = instance.web.Widget.extend({
template: 'SearchView.InputView',
start: function () {
var p = this._super.apply(this, arguments);
this.$element.on('focus', this.proxy('onFocus'));
this.$element.on('blur', this.proxy('onBlur'));
this.$element.on('keydown', this.proxy('onKeydown'));
this.$el.on('focus', this.proxy('onFocus'));
this.$el.on('blur', this.proxy('onBlur'));
this.$el.on('keydown', this.proxy('onKeydown'));
return p;
},
onFocus: function () {
this.trigger('focused', this);
},
onBlur: function () {
this.$element.text('');
this.$el.text('');
this.trigger('blurred', this);
},
getSelection: function () {
// get Text node
var root = this.$element[0].childNodes[0];
var root = this.$el[0].childNodes[0];
if (!root || !root.textContent) {
// if input does not have a child node, or the child node is an
// empty string, then the selection can only be (0, 0)
@ -202,7 +202,7 @@ my.InputView = instance.web.Widget.extend({
break;
case $.ui.keyCode.RIGHT:
sel = this.getSelection();
var len = this.$element.text().length;
var len = this.$el.text().length;
if (sel.start !== len || sel.start !== sel.end) {
e.stopPropagation();
}
@ -223,17 +223,17 @@ my.FacetView = instance.web.Widget.extend({
},
start: function () {
var self = this;
this.$element.on('focus', function () { self.trigger('focused', self); });
this.$element.on('blur', function () { self.trigger('blurred', self); });
this.$element.on('click', function (e) {
this.$el.on('focus', function () { self.trigger('focused', self); });
this.$el.on('blur', function () { self.trigger('blurred', self); });
this.$el.on('click', function (e) {
if ($(e.target).is('.oe_facet_remove')) {
self.model.destroy();
return false;
}
self.$element.focus();
self.$el.focus();
e.stopPropagation();
});
this.$element.on('keydown', function (e) {
this.$el.on('keydown', function (e) {
var keys = $.ui.keyCode;
switch (e.which) {
case keys.BACKSPACE:
@ -242,7 +242,7 @@ my.FacetView = instance.web.Widget.extend({
return false;
}
});
var $e = self.$element.find('> span:last-child');
var $e = self.$el.find('> span:last-child');
var q = $.when(this._super());
return q.pipe(function () {
var values = self.model.values.map(function (value) {
@ -253,7 +253,7 @@ my.FacetView = instance.web.Widget.extend({
});
},
model_changed: function () {
this.$element.text(this.$element.text() + '*');
this.$el.text(this.$el.text() + '*');
}
});
my.FacetValueView = instance.web.Widget.extend({
@ -268,7 +268,7 @@ my.FacetValueView = instance.web.Widget.extend({
this._super();
},
model_changed: function () {
this.$element.text(this.$element.text() + '*');
this.$el.text(this.$el.text() + '*');
}
});
@ -313,7 +313,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
.on('add change reset remove', this.proxy('renderFacets'));
if (this.hidden) {
this.$element.hide();
this.$el.hide();
}
if (this.headless) {
this.ready.resolve();
@ -330,7 +330,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
});
}
this.$element.on('keydown',
// Launch a search on clicking the oe_searchview_search button
this.$el.on('click', 'button.oe_searchview_search', function (e) {
e.stopImmediatePropagation();
self.do_search();
});
this.$el.on('keydown',
'.oe_searchview_input, .oe_searchview_facet', function (e) {
switch(e.which) {
case $.ui.keyCode.LEFT:
@ -344,31 +350,31 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
}
});
this.$element.on('click', '.oe_searchview_clear', function (e) {
this.$el.on('click', '.oe_searchview_clear', function (e) {
e.stopImmediatePropagation();
self.query.reset();
});
this.$element.on('click', '.oe_searchview_unfold_drawer', function (e) {
this.$el.on('click', '.oe_searchview_unfold_drawer', function (e) {
e.stopImmediatePropagation();
self.$element.toggleClass('oe_searchview_open_drawer');
self.$el.toggleClass('oe_searchview_open_drawer');
});
instance.web.bus.on('click', this, function(ev) {
if ($(ev.target).parents('.oe_searchview').length === 0) {
self.$element.removeClass('oe_searchview_open_drawer');
self.$el.removeClass('oe_searchview_open_drawer');
}
});
// Focus last input if the view itself is clicked (empty section of
// facets element)
this.$element.on('click', function (e) {
if (e.target === self.$element.find('.oe_searchview_facets')[0]) {
self.$element.find('.oe_searchview_input:last').focus();
this.$el.on('click', function (e) {
if (e.target === self.$el.find('.oe_searchview_facets')[0]) {
self.$el.find('.oe_searchview_input:last').focus();
}
});
// when the completion list opens/refreshes, automatically select the
// first completion item so if the user just hits [RETURN] or [TAB] it
// automatically selects it
this.$element.on('autocompleteopen', function () {
var menu = self.$element.data('autocomplete').menu;
this.$el.on('autocompleteopen', function () {
var menu = self.$el.data('autocomplete').menu;
menu.activate(
$.Event({ type: "mouseenter" }),
menu.element.children().first());
@ -377,15 +383,15 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
return $.when(p, this.ready);
},
show: function () {
this.$element.show();
this.$el.show();
},
hide: function () {
this.$element.hide();
this.$el.hide();
},
subviewForRoot: function (subview_root) {
return _(this.input_subviews).detect(function (subview) {
return subview.$element[0] === subview_root;
return subview.$el[0] === subview_root;
});
},
siblingSubview: function (subview, direction, wrap_around) {
@ -400,12 +406,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
focusPreceding: function (subview_root) {
return this.siblingSubview(
this.subviewForRoot(subview_root), -1, true)
.$element.focus();
.$el.focus();
},
focusFollowing: function (subview_root) {
return this.siblingSubview(
this.subviewForRoot(subview_root), +1, true)
.$element.focus();
.$el.focus();
},
/**
@ -424,19 +430,19 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
// autocomplete only correctly handles being initialized on the actual
// editable element (and only an element with a @value in 1.8 e.g.
// input or textarea), cheat by setting val() on $element
this.$element.on('keydown', function () {
// input or textarea), cheat by setting val() on $el
this.$el.on('keydown', function () {
// keydown is triggered *before* the element's value is set, so
// delay this. Pray that setTimeout are executed in FIFO (if they
// have the same delay) as autocomplete uses the exact same trick.
// FIXME: brittle as fuck
setTimeout(function () {
self.$element.val(self.currentInputValue());
self.$el.val(self.currentInputValue());
}, 0);
});
this.$element.autocomplete({
this.$el.autocomplete({
source: this.proxy('complete_global_search'),
select: this.proxy('select_completion'),
focus: function (e) { e.preventDefault(); },
@ -473,7 +479,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
* div[contenteditable].oe_searchview_input)
*/
currentInputValue: function () {
return this.$element.find('div.oe_searchview_input:focus').text();
return this.$el.find('div.oe_searchview_input:focus').text();
},
/**
* Provide auto-completion result for req.term (an array to `resp`)
@ -503,14 +509,23 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
var input_index = _(this.input_subviews).indexOf(
this.subviewForRoot(
this.$element.find('div.oe_searchview_input:focus')[0]));
this.$el.find('div.oe_searchview_input:focus')[0]));
this.query.add(ui.item.facet, {at: input_index / 2});
},
childFocused: function () {
this.$element.addClass('oe_focused');
this.$el.addClass('oe_focused');
},
childBlurred: function () {
this.$element.removeClass('oe_focused');
var val = this.$el.val();
this.$el.val('');
var complete = this.$el.data('autocomplete');
if ((val && complete.term === undefined) || complete.previous !== undefined) {
throw new Error("new jquery.ui version altering implementation" +
" details relied on");
}
delete complete.term;
this.$el.removeClass('oe_focused')
.trigger('blur');
},
/**
*
@ -523,7 +538,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
// _2: undefined if event=change, otherwise model
var self = this;
var started = [];
var $e = this.$element.find('div.oe_searchview_facets');
var $e = this.$el.find('div.oe_searchview_facets');
_.invoke(this.input_subviews, 'destroy');
this.input_subviews = [];
@ -554,7 +569,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
input_to_focus = self.input_subviews[(options.at + 1) * 2];
}
input_to_focus.$element.focus();
input_to_focus.$el.focus();
});
},
@ -620,6 +635,16 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
return null;
}
},
add_common_inputs: function() {
// add Filters to this.inputs, need view.controls filled
(new instance.web.search.Filters(this));
// add custom filters to this.inputs
(new instance.web.search.CustomFilters(this));
// add Advanced to this.inputs
(new instance.web.search.Advanced(this));
},
on_loaded: function(data) {
var self = this;
this.fields_view = data.fields_view;
@ -634,20 +659,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
data.fields_view['arch'].children,
data.fields_view.fields);
// add Filters to this.inputs, need view.controls filled
(new instance.web.search.Filters(this));
// add custom filters to this.inputs
(new instance.web.search.CustomFilters(this));
// add Advanced to this.inputs
(new instance.web.search.Advanced(this));
this.add_common_inputs();
// build drawer
var drawer_started = $.when.apply(
null, _(this.select_for_drawer()).invoke(
'appendTo', this.$element.find('.oe_searchview_drawer')));
'appendTo', this.$el.find('.oe_searchview_drawer')));
new instance.web.search.AddToDashboard(this).appendTo($('.oe_searchview_drawer', this.$element));
// load defaults
var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
'facet_for_defaults', this.defaults)).then(function () {
@ -662,15 +680,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
*/
on_filters_management: function(e) {
var self = this;
var select = this.$element.find(".oe_search-view-filters-management");
var select = this.$el.find(".oe_search-view-filters-management");
var val = select.val();
switch(val) {
case 'advanced_filter':
this.extended_search.on_activate();
break;
case 'add_to_dashboard':
this.on_add_to_dashboard();
break;
case '':
this.do_clear();
}
@ -846,13 +861,13 @@ instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web
);
}
});
instance.web.search.Widget = instance.web.OldWidget.extend( /** @lends instance.web.search.Widget# */{
instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web.search.Widget# */{
template: null,
/**
* Root class of all search widgets
*
* @constructs instance.web.search.Widget
* @extends instance.web.OldWidget
* @extends instance.web.Widget
*
* @param view the ancestor view of this widget
*/
@ -974,7 +989,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
this.view.query.on('add remove change reset', this.proxy('search_change'));
},
start: function () {
this.$element.on('click', 'li', this.proxy('toggle_filter'));
this.$el.on('click', 'li', this.proxy('toggle_filter'));
return $.when(null);
},
/**
@ -983,7 +998,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
*/
search_change: function () {
var self = this;
var $filters = this.$element.find('> li').removeClass('oe_selected');
var $filters = this.$el.find('> li').removeClass('oe_selected');
var facet = this.view.query.find(_.bind(this.match_facet, this));
if (!facet) { return; }
facet.values.each(function (v) {
@ -1284,17 +1299,17 @@ instance.web.search.CharField = instance.web.search.Field.extend( /** @lends ins
});
instance.web.search.NumberField = instance.web.search.Field.extend(/** @lends instance.web.search.NumberField# */{
value_from: function () {
if (!this.$element.val()) {
if (!this.$el.val()) {
return null;
}
var val = this.parse(this.$element.val()),
check = Number(this.$element.val());
var val = this.parse(this.$el.val()),
check = Number(this.$el.val());
if (isNaN(val) || val !== check) {
this.$element.addClass('error');
this.$el.addClass('error');
throw new instance.web.search.Invalid(
this.attrs.name, this.$element.val(), this.error_message);
this.attrs.name, this.$el.val(), this.error_message);
}
this.$element.removeClass('error');
this.$el.removeClass('error');
return val;
}
});
@ -1475,7 +1490,17 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
facet_for: function (value) {
var self = this;
if (value instanceof Array) {
return $.when(facet_from(this, value));
if (value.length === 2 && _.isString(value[1])) {
return $.when(facet_from(this, value));
}
if (value.length > 1) {
// more than one search_default m2o id? Should we OR them?
throw new Error(
_("M2O search fields do not currently handle multiple default values"));
}
// there are many cases of {search_default_$m2ofield: [id]}, need
// to handle this as if it were a single value.
value = value[0];
}
return this.model.call('name_get', [value], {}).pipe(function (names) {
if (_(names).isEmpty()) { return null; }
@ -1517,9 +1542,9 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
self.clear_selection();
})
.on('reset', this.proxy('clear_selection'));
this.$element.on('submit', 'form', this.proxy('save_current'));
this.$element.on('click', 'h4', function () {
self.$element.toggleClass('oe_opened');
this.$el.on('submit', 'form', this.proxy('save_current'));
this.$el.on('click', 'h4', function () {
self.$el.toggleClass('oe_opened');
});
// FIXME: local eval of domain and context to get rid of special endpoint
return this.rpc('/web/searchview/get_filters', {
@ -1527,7 +1552,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
}).pipe(this.proxy('set_filters'));
},
clear_selection: function () {
this.$element.find('li.oe_selected').removeClass('oe_selected');
this.$el.find('li.oe_selected').removeClass('oe_selected');
},
append_filter: function (filter) {
var self = this;
@ -1539,7 +1564,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
} else {
var id = filter.id;
$filter = this.filters[key] = $('<li></li>')
.appendTo(this.$element.find('.oe_searchview_custom_list'))
.appendTo(this.$el.find('.oe_searchview_custom_list'))
.addClass(filter.user_id ? 'oe_searchview_custom_private'
: 'oe_searchview_custom_public')
.text(filter.name);
@ -1574,8 +1599,8 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
},
save_current: function () {
var self = this;
var $name = this.$element.find('input:first');
var private_filter = !this.$element.find('input:last').prop('checked');
var $name = this.$el.find('input:first');
var private_filter = !this.$el.find('input:last').prop('checked');
var search = this.view.build_search_data();
this.rpc('/web/session/eval_domain_and_context', {
@ -1588,7 +1613,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
}
var filter = {
name: $name.val(),
user_id: private_filter ? instance.connection.uid : false,
user_id: private_filter ? instance.session.uid : false,
model_id: self.view.model,
context: results.context,
domain: results.domain
@ -1597,7 +1622,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
return self.model.call('create_or_replace', [filter]).then(function (id) {
filter.id = id;
self.append_filter(filter);
self.$element
self.$el
.removeClass('oe_opened')
.find('form')[0].reset();
});
@ -1642,8 +1667,8 @@ instance.web.search.Filters = instance.web.search.Input.extend({
}
return $.when(
this.render_column(col1, $('<div>').appendTo(this.$element)),
this.render_column(col2, $('<div>').appendTo(this.$element)));
this.render_column(col1, $('<div>').appendTo(this.$el)),
this.render_column(col2, $('<div>').appendTo(this.$el)));
},
render_column: function (column, $el) {
return $.when.apply(null, _(column).map(function (group) {
@ -1653,86 +1678,16 @@ instance.web.search.Filters = instance.web.search.Input.extend({
}));
}
});
instance.web.search.AddToDashboard = instance.web.Widget.extend({
template: 'SearchView.addtodashboard',
_in_drawer: true,
start: function () {
var self = this;
this.data_loaded = $.Deferred();
this.dashboard_data =[];
this.$element
.on('click', 'h4', this.proxy('show_option'))
.on('submit', 'form', function (e) {
e.preventDefault();
self.add_dashboard();
});
return $.when(this.load_data(),this.data_loaded).pipe(this.proxy("render_data"));
},
load_data:function(){
var self = this,dashboard_menu = instance.webclient.menu.data.data.children;
var ir_model_data = new instance.web.Model('ir.model.data',{},[['name','=','menu_reporting_dashboard']]).query(['res_id']);
var map_data = function(result){
_.detect(dashboard_menu, function(dash){
var id = _.pluck(dash.children, "id"),indexof = _.indexOf(id, result.res_id);
if(indexof !== -1){
self.dashboard_data = dash.children[indexof].children
self.data_loaded.resolve();
return;
}
});
};
return ir_model_data._execute().done(function(result){map_data(result[0])});
},
render_data: function(){
var self = this;
var selection = instance.web.qweb.render("SearchView.addtodashboard.selection",{selections:this.dashboard_data});
this.$element.find("input").before(selection)
},
add_dashboard:function(){
var self = this;
var getParent = this.getParent();
var view_parent = this.getParent().getParent();
if (! view_parent.action || ! this.$element.find("select").val())
return this.do_warn("Can't find dashboard action");
data = getParent.build_search_data(),
context = new instance.web.CompoundContext(getParent.dataset.get_context() || []),
domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []);
_.each(data.contexts, function(x) {context.add(x);});
_.each(data.domains, function(x) {domain.add(x);});
this.rpc('/web/searchview/add_to_dashboard', {
menu_id: this.$element.find("select").val(),
action_id: view_parent.action.id,
context_to_save: context,
domain: domain,
view_mode: view_parent.active_view,
name: this.$element.find("input").val()
}, function(r) {
if (r === false) {
self.do_warn("Could not add filter to dashboard");
} else {
self.$element.toggleClass('oe_opened');
self.do_notify("Filter added to dashboard", '');
}
});
},
show_option:function(){
this.$element.toggleClass('oe_opened');
if (! this.$element.hasClass('oe_opened'))
return;
this.$element.find("input").val(this.getParent().fields_view.name || "" );
}
});
instance.web.search.Advanced = instance.web.search.Input.extend({
template: 'SearchView.advanced',
_in_drawer: true,
start: function () {
var self = this;
this.$element
this.$el
.on('keypress keydown keyup', function (e) { e.stopPropagation(); })
.on('click', 'h4', function () {
self.$element.toggleClass('oe_opened');
self.$el.toggleClass('oe_opened');
}).on('click', 'button.oe_add_condition', function () {
self.append_proposition();
}).on('submit', 'form', function (e) {
@ -1751,7 +1706,7 @@ instance.web.search.Advanced = instance.web.search.Input.extend({
},
append_proposition: function () {
return (new instance.web.search.ExtendedSearchProposition(this, this.fields))
.appendTo(this.$element.find('ul'));
.appendTo(this.$el.find('ul'));
},
commit_search: function () {
var self = this;
@ -1778,15 +1733,15 @@ instance.web.search.Advanced = instance.web.search.Input.extend({
// add new empty proposition
this.append_proposition();
// TODO: API on searchview
this.view.$element.removeClass('oe_searchview_open_drawer');
this.view.$el.removeClass('oe_searchview_open_drawer');
}
});
instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{
instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{
template: 'SearchView.extended_search.proposition',
/**
* @constructs instance.web.search.ExtendedSearchProposition
* @extends instance.web.OldWidget
* @extends instance.web.Widget
*
* @param parent
* @param fields
@ -1802,16 +1757,16 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/*
},
start: function () {
var _this = this;
this.$element.find(".searchview_extended_prop_field").change(function() {
this.$el.find(".searchview_extended_prop_field").change(function() {
_this.changed();
});
this.$element.find('.searchview_extended_delete_prop').click(function () {
this.$el.find('.searchview_extended_delete_prop').click(function () {
_this.destroy();
});
this.changed();
},
changed: function() {
var nval = this.$element.find(".searchview_extended_prop_field").val();
var nval = this.$el.find(".searchview_extended_prop_field").val();
if(this.attrs.selected == null || nval != this.attrs.selected.name) {
this.select_field(_.detect(this.fields, function(x) {return x.name == nval;}));
}
@ -1826,7 +1781,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/*
if(this.attrs.selected != null) {
this.value.destroy();
this.value = null;
this.$element.find('.searchview_extended_prop_op').html('');
this.$el.find('.searchview_extended_prop_op').html('');
}
this.attrs.selected = field;
if(field == null) {
@ -1842,9 +1797,9 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/*
_.each(this.value.operators, function(operator) {
$('<option>', {value: operator.value})
.text(String(operator.text))
.appendTo(self.$element.find('.searchview_extended_prop_op'));
.appendTo(self.$el.find('.searchview_extended_prop_op'));
});
var $value_loc = this.$element.find('.searchview_extended_prop_value').empty();
var $value_loc = this.$el.find('.searchview_extended_prop_value').empty();
this.value.appendTo($value_loc);
},
@ -1852,7 +1807,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/*
if ( this.attrs.selected == null)
return null;
var field = this.attrs.selected;
var op = this.$element.find('.searchview_extended_prop_op')[0];
var op = this.$el.find('.searchview_extended_prop_op')[0];
var operator = op.options[op.selectedIndex];
return {
label: _.str.sprintf(_t('%(field)s %(operator)s "%(value)s"'), {
@ -1896,7 +1851,7 @@ instance.web.search.ExtendedSearchProposition.Char = instance.web.search.Extende
{value: "!=", text: _lt("is not equal to")}
],
get_value: function() {
return this.$element.val();
return this.$el.val();
}
});
instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.ExtendedSearchProposition.Field.extend({
@ -1920,7 +1875,7 @@ instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.Ext
start: function() {
var ready = this._super();
this.datewidget = new (this.widget())(this);
this.datewidget.appendTo(this.$element);
this.datewidget.appendTo(this.$el);
return ready;
}
});
@ -1938,11 +1893,11 @@ instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.Exte
{value: "<=", text: _lt("less or equal than")}
],
toString: function () {
return this.$element.val();
return this.$el.val();
},
get_value: function() {
try {
return instance.web.parse_value(this.$element.val(), {'widget': 'integer'});
return instance.web.parse_value(this.$el.val(), {'widget': 'integer'});
} catch (e) {
return "";
}
@ -1962,11 +1917,11 @@ instance.web.search.ExtendedSearchProposition.Float = instance.web.search.Extend
{value: "<=", text: _lt("less or equal than")}
],
toString: function () {
return this.$element.val();
return this.$el.val();
},
get_value: function() {
try {
return instance.web.parse_value(this.$element.val(), {'widget': 'float'});
return instance.web.parse_value(this.$el.val(), {'widget': 'float'});
} catch (e) {
return "";
}
@ -1979,12 +1934,12 @@ instance.web.search.ExtendedSearchProposition.Selection = instance.web.search.Ex
{value: "!=", text: _lt("is not")}
],
toString: function () {
var select = this.$element[0];
var select = this.$el[0];
var option = select.options[select.selectedIndex];
return option.label || option.text;
},
get_value: function() {
return this.$element.val();
return this.$el.val();
}
});
instance.web.search.ExtendedSearchProposition.Boolean = instance.web.search.ExtendedSearchProposition.Field.extend({

View File

@ -1,18 +1,17 @@
openerp.test_support = {
setup_connection: function (connection) {
setup_session: function (session) {
var origin = location.protocol+"//"+location.host;
_.extend(connection, {
_.extend(session, {
origin: origin,
prefix: origin,
server: origin, // keep chs happy
//openerp.web.qweb.default_dict['_s'] = this.origin;
rpc_function: connection.rpc_json,
rpc_function: session.rpc_json,
session_id: false,
uid: false,
username: false,
user_context: {},
db: false,
openerp_entreprise: false,
// this.module_list = openerp._modules.slice();
// this.module_loaded = {};
// _(this.module_list).each(function (mod) {
@ -22,7 +21,7 @@ openerp.test_support = {
shortcuts: [],
active_id: null
});
return connection.session_reload();
return session.session_reload();
},
module: function (title, tested_core, nonliterals) {
var conf = QUnit.config.openerp = {};
@ -31,10 +30,10 @@ openerp.test_support = {
QUnit.stop();
var oe = conf.openerp = window.openerp.init();
window.openerp.web[tested_core](oe);
var done = openerp.test_support.setup_connection(oe.connection);
var done = openerp.test_support.setup_session(oe.session);
if (nonliterals) {
done = done.pipe(function () {
return oe.connection.rpc('/tests/add_nonliterals', {
return oe.session.rpc('/tests/add_nonliterals', {
domains: nonliterals.domains || [],
contexts: nonliterals.contexts || []
}).then(function (r) {

File diff suppressed because it is too large Load Diff

View File

@ -21,9 +21,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
// whether the view rows can be reordered (via vertical drag & drop)
'reorderable': true,
'action_buttons': true,
// if true, the view can't be editable, ignoring the view's and the context's
// instructions
'read_only': false,
// if true, the 'Import', 'Export', etc... buttons will be shown
'import_enabled': true,
},
@ -142,13 +139,13 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
});
},
/**
* View startup method, the default behavior is to set the ``oe_listw``
* View startup method, the default behavior is to set the ``oe_list``
* class on its root element and to perform an RPC load call.
*
* @returns {$.Deferred} loading promise
*/
start: function() {
this.$element.addClass('oe_list');
this.$el.addClass('oe_list');
return this.reload_view(null, null, true);
},
/**
@ -251,20 +248,20 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
this.setup_columns(this.fields_view.fields, grouped);
this.$element.html(QWeb.render(this._template, this));
this.$element.addClass(this.fields_view.arch.attrs['class']);
this.$el.html(QWeb.render(this._template, this));
this.$el.addClass(this.fields_view.arch.attrs['class']);
// Head hook
// Selecting records
this.$element.find('.oe_list_record_selector').click(function(){
self.$element.find('.oe_list_record_selector input').prop('checked',
self.$element.find('.oe_list_record_selector').prop('checked') || false);
this.$el.find('.oe_list_record_selector').click(function(){
self.$el.find('.oe_list_record_selector input').prop('checked',
self.$el.find('.oe_list_record_selector').prop('checked') || false);
var selection = self.groups.get_selection();
$(self.groups).trigger(
'selected', [selection.ids, selection.records]);
});
// Sorting columns
this.$element.find('thead').delegate('th.oe_sortable[data-id]', 'click', function (e) {
this.$el.find('thead').delegate('th.oe_sortable[data-id]', 'click', function (e) {
e.stopPropagation();
var $this = $(this);
self.dataset.sort($this.data('id'));
@ -284,11 +281,11 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
if (this.options.$buttons) {
this.$buttons.appendTo(this.options.$buttons);
} else {
this.$element.find('.oe_list_buttons').replaceWith(this.$buttons);
this.$el.find('.oe_list_buttons').replaceWith(this.$buttons);
}
this.$buttons.find('.oe_list_add')
.click(this.proxy('do_add_record'))
.prop('disabled', grouped && this.options.editable);
.prop('disabled', grouped);
this.$buttons.on('click', '.oe_list_button_import', function() {
self.on_sidebar_import();
return false;
@ -301,7 +298,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
if (this.options.$buttons) {
this.$pager.appendTo(this.options.$pager);
} else {
this.$element.find('.oe_list_pager').replaceWith(this.$pager);
this.$el.find('.oe_list_pager').replaceWith(this.$pager);
}
this.$pager
@ -357,9 +354,10 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
this.sidebar.add_items('other', [
{ label: _t("Import"), callback: this.on_sidebar_import },
{ label: _t("Export"), callback: this.on_sidebar_export },
{ label: _t('Delete'), callback: this.do_delete_selected },
{ label: _t('Delete'), callback: this.do_delete_selected }
]);
this.sidebar.add_toolbar(this.fields_view.toolbar);
this.sidebar.$el.hide();
}
},
/**
@ -378,6 +376,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
var total = dataset.size();
var limit = this.limit() || total;
this.$pager.toggle(total !== 0);
this.$pager.toggleClass('oe_list_pager_single_page', (total <= limit));
var spager = '-';
if (total) {
@ -400,68 +399,23 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
* @param {Boolean} [grouped] Should the grouping columns (group and count) be displayed
*/
setup_columns: function (fields, grouped) {
var domain_computer = instance.web.form.compute_domain;
var noop = function () { return {}; };
var field_to_column = function (field) {
var name = field.attrs.name;
var column = _.extend({id: name, tag: field.tag},
fields[name], field.attrs);
// modifiers computer
if (column.modifiers) {
var modifiers = JSON.parse(column.modifiers);
column.modifiers_for = function (fields) {
if (!modifiers.invisible) {
return {};
}
return {
'invisible': domain_computer(modifiers.invisible, fields)
};
};
if (modifiers['tree_invisible']) {
column.invisible = '1';
} else {
delete column.invisible;
}
} else {
column.modifiers_for = noop;
}
return column;
};
var registry = instance.web.list.columns;
this.columns.splice(0, this.columns.length);
this.columns.push.apply(
this.columns,
_(this.fields_view.arch.children).map(field_to_column));
this.columns.push.apply(this.columns,
_(this.fields_view.arch.children).map(function (field) {
var id = field.attrs.name;
return registry.for_(id, fields[id], field);
}));
if (grouped) {
this.columns.unshift({
id: '_group', tag: '', string: _t("Group"), meta: true,
modifiers_for: function () { return {}; }
}, {
id: '_count', tag: '', string: '#', meta: true,
modifiers_for: function () { return {}; }
});
this.columns.unshift(
new instance.web.list.MetaColumn('_group', _t("Group")));
}
this.visible_columns = _.filter(this.columns, function (column) {
return column.invisible !== '1';
});
this.aggregate_columns = _(this.visible_columns)
.map(function (column) {
if (column.type !== 'integer' && column.type !== 'float') {
return {};
}
var aggregation_func = column['group_operator'] || 'sum';
if (!(aggregation_func in column)) {
return {};
}
return _.extend({}, column, {
'function': aggregation_func,
label: column[aggregation_func]
});
});
this.aggregate_columns = _(this.visible_columns).invoke('to_aggregate');
},
/**
* Used to handle a click on a table row, if no other handler caught the
@ -487,9 +441,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
},
do_show: function () {
this._super();
if (this.sidebar) {
this.sidebar.$element.show();
}
if (this.$buttons) {
this.$buttons.show();
}
@ -499,7 +450,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
},
do_hide: function () {
if (this.sidebar) {
this.sidebar.$element.hide();
this.sidebar.$el.hide();
}
if (this.$buttons) {
this.$buttons.hide();
@ -539,10 +490,10 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
*/
reload_content: function () {
var self = this;
self.$element.find('.oe_list_record_selector').prop('checked', false);
self.$el.find('.oe_list_record_selector').prop('checked', false);
this.records.reset();
var reloaded = $.Deferred();
this.$element.find('.oe_list_content').append(
this.$el.find('.oe_list_content').append(
this.groups.render(function () {
if (self.dataset.index == null) {
var has_one = false;
@ -644,7 +595,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
if (!ids.length) {
this.dataset.index = 0;
if (this.sidebar) {
this.sidebar.$element.hide();
this.sidebar.$el.hide();
}
this.compute_aggregates();
return;
@ -652,7 +603,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
this.dataset.index = _(this.dataset.ids).indexOf(ids[0]);
if (this.sidebar) {
this.sidebar.$element.show();
this.sidebar.$el.show();
}
this.compute_aggregates(_(records).map(function (record) {
@ -667,6 +618,23 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
* @param {Function} callback should be called after the action is executed, if non-null
*/
do_button_action: function (name, id, callback) {
this.handle_button(name, id, callback);
},
/**
* Base handling of buttons, can be called when overriding do_button_action
* in order to bypass parent overrides.
*
* The callback will be provided with the ``id`` as its parameter, in case
* handle_button's caller had to alter the ``id`` (or even create one)
* while not being ``callback``'s creator.
*
* This method should not be overridden.
*
* @param {String} name action name
* @param {Object} id id of the record the action should be called on
* @param {Function} callback should be called after the action is executed, if non-null
*/
handle_button: function (name, id, callback) {
var action = _.detect(this.columns, function (field) {
return field.name === name;
});
@ -685,7 +653,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
c.add(action.context);
}
action.context = c;
this.do_execute_action(action, this.dataset, id, callback);
this.do_execute_action(
action, this.dataset, id, _.bind(callback, null, id));
},
/**
* Handles the activation of a record (clicking on it)
@ -794,16 +763,14 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
},
display_aggregates: function (aggregation) {
var self = this;
var $footer_cells = this.$element.find('.oe_list_footer');
var $footer_cells = this.$el.find('.oe_list_footer');
_(this.aggregate_columns).each(function (column) {
if (!column['function']) {
return;
}
$footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id))
.html(instance.web.format_cell(aggregation, column, {
process_modifiers: false
}));
.html(column.format(aggregation, { process_modifiers: false }));
});
},
get_selected_ids: function() {
@ -822,7 +789,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
pad_columns: function (count, options) {
options = options || {};
// padding for action/pager header
var $first_header = this.$element.find('thead tr:first th');
var $first_header = this.$el.find('thead tr:first th');
var colspan = $first_header.attr('colspan');
if (colspan) {
if (!this.previous_colspan) {
@ -831,7 +798,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
$first_header.attr('colspan', parseInt(colspan, 10) + count);
}
// Padding for column titles, footer and data rows
var $rows = this.$element
var $rows = this.$el
.find('.oe_list_header_columns, tr:not(thead tr)')
.not(options['except']);
var newcols = new Array(count+1).join('<td class="oe_list_padding"></td>');
@ -845,26 +812,24 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
* Removes all padding columns of the table
*/
unpad_columns: function () {
this.$element.find('.oe_list_padding').remove();
this.$el.find('.oe_list_padding').remove();
if (this.previous_colspan) {
this.$element
this.$el
.find('thead tr:first th')
.attr('colspan', this.previous_colspan);
this.previous_colspan = null;
}
},
no_result: function () {
this.$element.find('.oe_view_nocontent').remove();
this.$el.find('.oe_view_nocontent').remove();
if (this.groups.group_by
|| !this.options.action
|| !this.options.action.help) {
return;
}
this.$element.find('table:first').hide();
this.$element.prepend(
$('<div class="oe_view_nocontent">')
.append($('<img>', { src: '/web/static/src/img/view_empty_arrow.png' }))
.append($('<div>').html(this.options.action.help))
this.$el.find('table:first').hide();
this.$el.prepend(
$('<div class="oe_view_nocontent">').html(this.options.action.help)
);
}
});
@ -911,32 +876,44 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
this.record_callbacks = {
'remove': function (event, record) {
var $row = self.$current.find(
'[data-id=' + record.get('id') + ']');
var $row = self.$current.children(
'[data-id=' + record.get('id') + ']');
var index = $row.data('index');
$row.remove();
self.refresh_zebra(index);
},
'reset': function () { return self.on_records_reset(); },
'change': function (event, record) {
var $row = self.$current.find('[data-id=' + record.get('id') + ']');
'change': function (event, record, attribute, value, old_value) {
var $row;
if (attribute === 'id') {
if (old_value) {
throw new Error("Setting 'id' attribute on existing record "
+ JSON.stringify(record.attributes));
}
if (!_.contains(self.dataset.ids, value)) {
// add record to dataset if not already in (added by
// the form view?)
self.dataset.ids.splice(
self.records.indexOf(record), 0, value);
}
// Set id on new record
$row = self.$current.children('[data-id=false]');
} else {
$row = self.$current.children(
'[data-id=' + record.get('id') + ']');
}
$row.replaceWith(self.render_record(record));
},
'add': function (ev, records, record, index) {
var $new_row = $('<tr>').attr({
'data-id': record.get('id')
});
var $new_row = $(self.render_record(record));
if (index === 0) {
$new_row.prependTo(self.$current);
} else {
var previous_record = records.at(index-1),
$previous_sibling = self.$current.find(
$previous_sibling = self.$current.children(
'[data-id=' + previous_record.get('id') + ']');
$new_row.insertAfter($previous_sibling);
}
self.refresh_zebra(index, 1);
}
};
_(this.record_callbacks).each(function (callback, event) {
@ -967,19 +944,19 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
// of digits, nice when storing actual numbers, not nice when
// storing strings composed only of digits. Force the action
// name to be a string
$(self).trigger('action', [field.toString(), record_id, function () {
return self.reload_record(self.records.get(record_id));
$(self).trigger('action', [field.toString(), record_id, function (id) {
return self.reload_record(self.records.get(id));
}]);
})
.delegate('a', 'click', function (e) {
e.stopPropagation();
})
.delegate('tr', 'click', function (e) {
e.stopPropagation();
var row_id = self.row_id(e.currentTarget);
if (row_id !== undefined) {
if (row_id) {
e.stopPropagation();
if (!self.dataset.select_id(row_id)) {
throw "Could not find id in dataset"
throw new Error("Could not find id in dataset");
}
self.row_clicked(e);
}
@ -1029,7 +1006,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
});
}
}
return instance.web.format_cell(record.toForm().data, column, {
return column.format(record.toForm().data, {
model: this.dataset.model,
id: record.get('id')
});
@ -1076,7 +1053,6 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
this.$current
.children('tr:not([data-id])').remove().end()
.append(new Array(count - this.records.length + 1).join(row));
this.refresh_zebra(this.records.length);
},
/**
* Gets the ids of all currently selected records, if any
@ -1139,6 +1115,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
* @returns {String} QWeb rendering of the selected record
*/
render_record: function (record) {
var self = this;
var index = this.records.indexOf(record);
return QWeb.render('ListView.row', {
columns: this.columns,
@ -1147,26 +1124,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
row_parity: (index % 2 === 0) ? 'even' : 'odd',
view: this.view,
render_cell: function () {
return this.render_cell.apply(this, arguments); }
});
},
/**
* Fixes fixes the even/odd classes
*
* @param {Number} [from_index] index from which to resequence
* @param {Number} [offset = 0] selection offset for DOM, in case there are rows to ignore in the table
*/
refresh_zebra: function (from_index, offset) {
offset = offset || 0;
from_index = from_index || 0;
var dom_offset = offset + from_index;
var sel = dom_offset ? ':gt(' + (dom_offset - 1) + ')' : null;
this.$current.children(sel).each(function (i, e) {
var index = from_index + i;
// reset record-index accelerators on rows and even/odd
var even = index%2 === 0;
$(e).toggleClass('even', even)
.toggleClass('odd', !even);
return self.render_cell.apply(self, arguments); }
});
}
});
@ -1333,15 +1291,18 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
_t("Grouping on field '%s' is not possible because that field does not appear in the list view."),
group.grouped_on));
}
var group_label;
try {
$group_column.html(instance.web.format_cell(
row_data, group_column, {
value_if_empty: _t("Undefined"),
process_modifiers: false
}));
group_label = group_column.format(row_data, {
value_if_empty: _t("Undefined"),
process_modifiers: false
});
} catch (e) {
$group_column.html(row_data[group_column.id].value);
group_label = row_data[group_column.id].value;
}
$group_column.text(_.str.sprintf("%s (%d)",
group_label, group.length));
if (group.length && group.openable) {
// Make openable if not terminal group & group_by_no_leaf
$group_column.prepend('<span class="ui-icon ui-icon-triangle-1-e" style="float: left;">');
@ -1353,14 +1314,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
}
}
self.indent($group_column, group.level);
// count column
$('<td>').text(group.length).appendTo($row);
if (self.options.selectable) {
$row.append('<td>');
}
_(self.columns).chain()
.filter(function (column) {return !column.invisible;})
.filter(function (column) { return column.invisible !== '1'; })
.each(function (column) {
if (column.meta) {
// do not do anything
@ -1368,8 +1327,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
var r = {};
r[column.id] = {value: group.aggregates[column.id]};
$('<td class="oe_number">')
.html(instance.web.format_cell(
r, column, {process_modifiers: false}))
.html(column.format(r, {process_modifiers: false}))
.appendTo($row);
} else {
$row.append('<td>');
@ -1456,18 +1414,31 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
},
setup_resequence_rows: function (list, dataset) {
// drag and drop enabled if list is not sorted and there is a
// "sequence" column in the view.
// visible column with @widget=handle or "sequence" column in the view.
if ((dataset.sort && dataset.sort())
|| !_(this.columns).any(function (column) {
return column.name === 'sequence'; })) {
return column.widget === 'handle'
|| column.name === 'sequence'; })) {
return;
}
var sequence_field = _(this.columns).find(function (c) {
return c.widget === 'handle';
});
var seqname = sequence_field ? sequence_field.name : 'sequence';
// ondrop, move relevant record & fix sequences
list.$current.sortable({
axis: 'y',
items: '> tr[data-id]',
containment: 'parent',
helper: 'clone',
helper: 'clone'
});
if (sequence_field) {
list.$current.sortable('option', 'handle', '.oe_list_field_handle');
}
list.$current.sortable('option', {
start: function (e, ui) {
ui.placeholder.height(ui.item.height());
},
stop: function (event, ui) {
var to_move = list.records.get(ui.item.data('id')),
target_id = ui.item.prev().data('id'),
@ -1485,7 +1456,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
var record, index = to,
// if drag to 1st row (to = 0), start sequencing from 0
// (exclusive lower bound)
seq = to ? list.records.at(to - 1).get('sequence') : 0;
seq = to ? list.records.at(to - 1).get(seqname) : 0;
while (++seq, record = list.records.at(index++)) {
// write are independent from one another, so we can just
// launch them all at the same time and we don't really
@ -1495,39 +1466,39 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
// when synchronous (without setTimeout)
(function (dataset, id, seq) {
$.async_when().then(function () {
dataset.write(id, {sequence: seq});
var attrs = {};
attrs[seqname] = seq;
dataset.write(id, attrs);
});
}(dataset, record.get('id'), seq));
record.set('sequence', seq);
record.set(seqname, seq);
}
list.refresh_zebra();
}
});
},
render: function (post_render) {
var self = this;
var $element = $('<tbody>');
this.elements = [$element[0]];
var $el = $('<tbody>');
this.elements = [$el[0]];
this.datagroup.list(
_(this.view.visible_columns).chain()
.filter(function (column) { return column.tag === 'field' })
.pluck('name').value(),
function (groups) {
$element[0].appendChild(
$el[0].appendChild(
self.render_groups(groups));
if (post_render) { post_render(); }
}, function (dataset) {
self.render_dataset(dataset).then(function (list) {
self.children[null] = list;
self.elements =
[list.$current.replaceAll($element)[0]];
[list.$current.replaceAll($el)[0]];
self.setup_resequence_rows(list, dataset);
if (post_render) { post_render(); }
});
});
return $element;
return $el;
},
/**
* Returns the ids of all selected records for this group, and the records
@ -1831,7 +1802,7 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{
* @returns this
*/
remove: function (record) {
var index = _(this.records).indexOf(record);
var index = this.indexOf(record);
if (index === -1) {
_(this._proxies).each(function (proxy) {
proxy.remove(record);
@ -1847,13 +1818,42 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{
return this;
},
_onRecordEvent: function (event, record, options) {
_onRecordEvent: function (event) {
switch(event) {
// don't propagate reset events
if (event === 'reset') { return; }
case 'reset': return;
case 'change:id':
var record = arguments[1];
var new_value = arguments[2];
var old_value = arguments[3];
// [change:id, record, new_value, old_value]
if (this._byId[old_value] === record) {
delete this._byId[old_value];
this._byId[new_value] = record;
}
break;
}
this.trigger.apply(this, arguments);
},
// underscore-type methods
find: function (callback) {
var record;
for(var section in this._proxies) {
if (!this._proxies.hasOwnProperty(section)) {
continue
}
if ((record = this._proxies[section].find(callback))) {
return record;
}
}
for(var i=0; i<this.length; ++i) {
record = this.records[i];
if (callback(record)) {
return record;
}
}
},
each: function (callback) {
for(var section in this._proxies) {
if (this._proxies.hasOwnProperty(section)) {
@ -1878,6 +1878,46 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{
},
indexOf: function (record) {
return _(this.records).indexOf(record);
},
succ: function (record, options) {
options = options || {wraparound: false};
var result;
for(var section in this._proxies) {
if (!this._proxies.hasOwnProperty(section)) {
continue;
}
if ((result = this._proxies[section].succ(record, options))) {
return result;
}
}
var index = this.indexOf(record);
if (index === -1) { return null; }
var next_index = index + 1;
if (options.wraparound && (next_index === this.length)) {
return this.at(0);
}
return this.at(next_index);
},
pred: function (record, options) {
options = options || {wraparound: false};
var result;
for (var section in this._proxies) {
if (!this._proxies.hasOwnProperty(section)) {
continue;
}
if ((result = this._proxies[section].pred(record, options))) {
return result;
}
}
var index = this.indexOf(record);
if (index === -1) { return null; }
var next_index = index - 1;
if (options.wraparound && (next_index === -1)) {
return this.at(this.length - 1);
}
return this.at(next_index);
}
});
Collection.include(Events);
@ -1885,6 +1925,204 @@ instance.web.list = {
Events: Events,
Record: Record,
Collection: Collection
}
};
/**
* Registry for column objects used to format table cells (and some other tasks
* e.g. aggregation computations).
*
* Maps a field or button to a Column type via its ``$tag.$widget``,
* ``$tag.$type`` or its ``$tag`` (alone).
*
* This specific registry has a dedicated utility method ``for_`` taking a
* field (from fields_get/fields_view_get.field) and a node (from a view) and
* returning the right object *already instantiated from the data provided*.
*
* @type {instance.web.Registry}
*/
instance.web.list.columns = new instance.web.Registry({
'field': 'instance.web.list.Column',
'field.boolean': 'instance.web.list.Boolean',
'field.binary': 'instance.web.list.Binary',
'field.progressbar': 'instance.web.list.ProgressBar',
'field.handle': 'instance.web.list.Handle',
'button': 'instance.web.list.Button',
});
instance.web.list.columns.for_ = function (id, field, node) {
var description = _.extend({tag: node.tag}, field, node.attrs);
var tag = description.tag;
var Type = this.get_any([
tag + '.' + description.widget,
tag + '.'+ description.type,
tag
]);
return new Type(id, node.tag, description)
};
instance.web.list.Column = instance.web.Class.extend({
init: function (id, tag, attrs) {
_.extend(attrs, {
id: id,
tag: tag
});
this.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
delete attrs.modifiers;
_.extend(this, attrs);
if (this.modifiers['tree_invisible']) {
this.invisible = '1';
} else { delete this.invisible; }
},
modifiers_for: function (fields) {
var out = {};
var domain_computer = instance.web.form.compute_domain;
for (var attr in this.modifiers) {
if (!this.modifiers.hasOwnProperty(attr)) { continue; }
var modifier = this.modifiers[attr];
out[attr] = _.isBoolean(modifier)
? modifier
: domain_computer(modifier, fields);
}
return out;
},
to_aggregate: function () {
if (this.type !== 'integer' && this.type !== 'float') {
return {};
}
var aggregation_func = this['group_operator'] || 'sum';
if (!(aggregation_func in this)) {
return {};
}
var C = function (label, fn) {
this['function'] = fn;
this.label = label;
};
C.prototype = this;
return new C(aggregation_func, this[aggregation_func]);
},
/**
*
* @param row_data record whose values should be displayed in the cell
* @param {Object} [options]
* @param {String} [options.value_if_empty=''] what to display if the field's value is ``false``
* @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ?
* @param {String} [options.model] current record's model
* @param {Number} [options.id] current record's id
* @return {String}
*/
format: function (row_data, options) {
options = options || {};
var attrs = {};
if (options.process_modifiers !== false) {
attrs = this.modifiers_for(row_data);
}
if (attrs.invisible) { return ''; }
if (!row_data[this.id]) {
return options.value_if_empty === undefined
? ''
: options.value_if_empty;
}
return this._format(row_data, options);
},
/**
* Method to override in order to provide alternative HTML content for the
* cell. Column._format will simply call ``instance.web.format_value`` and
* escape the output.
*
* The output of ``_format`` will *not* be escaped by ``format``, any
* escaping *must be done* by ``format``.
*
* @private
*/
_format: function (row_data, options) {
return _.escape(instance.web.format_value(
row_data[this.id].value, this, options.value_if_empty));
}
});
instance.web.list.MetaColumn = instance.web.list.Column.extend({
meta: true,
init: function (id, string) {
this._super(id, '', {string: string});
}
});
instance.web.list.Button = instance.web.list.Column.extend({
/**
* Return an actual ``<button>`` tag
*/
format: function (row_data, options) {
return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' +
'<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' +
'</button>', {
title: this.string || '',
additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ?
'disabled="disabled" class="oe_list_button_disabled"' : '',
prefix: instance.session.prefix,
icon: this.icon,
alt: this.string || ''
});
}
});
instance.web.list.Boolean = instance.web.list.Column.extend({
/**
* Return a potentially disabled checkbox input
*
* @private
*/
_format: function (row_data, options) {
return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
row_data[this.id].value ? 'checked="checked"' : '');
}
});
instance.web.list.Binary = instance.web.list.Column.extend({
/**
* Return a link to the binary data as a file
*
* @private
*/
_format: function (row_data, options) {
var text = _t("Download");
var download_url = _.str.sprintf(
'/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d',
instance.session.session_id, options.model, this.id, options.id);
if (this.filename) {
download_url += '&filename_field=' + this.filename;
if (row_data[this.filename]) {
text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
row_data[this.filename].value, {type: 'char'}));
}
}
return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
text: text,
href: download_url,
size: row_data[this.id].value
});
}
});
instance.web.list.ProgressBar = instance.web.list.Column.extend({
/**
* Return a formatted progress bar display
*
* @private
*/
_format: function (row_data, options) {
return _.template(
'<progress value="<%-value%>" max="100"><%-value%>%</progress>', {
value: _.str.sprintf("%.0f", row_data[this.id].value || 0)
});
}
});
instance.web.list.Handle = instance.web.list.Column.extend({
/**
* Return styling hooks for a drag handle
*
* @private
*/
_format: function (row_data, options) {
return '<div class="oe_list_handle">';
}
});
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:

File diff suppressed because it is too large Load Diff

View File

@ -84,13 +84,13 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
});
this.fields = fields_view.fields;
this.hook_row_click();
this.$element.html(QWeb.render('TreeView', {
this.$el.html(QWeb.render('TreeView', {
'title': this.fields_view.arch.attrs.string,
'fields_view': this.fields_view.arch.children,
'fields': this.fields,
'toolbar': has_toolbar
}));
this.$element.addClass(this.fields_view.arch.attrs['class']);
this.$el.addClass(this.fields_view.arch.attrs['class']);
this.dataset.read_slice(this.fields_list()).then(function(records) {
self.store_record(records);
@ -102,7 +102,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
return;
}
var $select = self.$element.find('select')
var $select = self.$el.find('select')
.change(function () {
var $option = $(this).find(':selected');
self.getdata($option.val());
@ -120,10 +120,10 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
$select.change();
}
});
this.$element.find("#tree_view_expand").click(function(){
this.$el.find("#tree_view_expand").click(function(){
self.expand_all();
});
this.$element.find("#tree_view_collapse").click(function(){
this.$el.find("#tree_view_collapse").click(function(){
self.collpase_all();
});
// TODO store open nodes in url ?...
@ -143,14 +143,14 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
},
expand_all: function(){
var self = this;
var tr = this.$element.find(".oe-treeview-table tbody tr[id^='treerow_']");
var tr = this.$el.find(".oe-treeview-table tbody tr[id^='treerow_']");
_.each(tr,function(rec){
self.showcontent($(rec).attr('data-id'),true);
});
},
collpase_all: function(){
var self = this;
var root_tr = this.$element.find(".oe-treeview-table tbody tr[data-level='"+1+"']");
var root_tr = this.$el.find(".oe-treeview-table tbody tr[data-level='"+1+"']");
_.each(root_tr,function(rec){
if($(rec).hasClass('oe_open')){
self.showcontent($(rec).attr('data-id'),false);
@ -187,12 +187,12 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
*/
hook_row_click: function () {
var self = this;
this.$element.delegate('.treeview-td span, .treeview-tr span', 'click', function (e) {
this.$el.delegate('.treeview-td span, .treeview-tr span', 'click', function (e) {
e.stopImmediatePropagation();
self.activate($(this).closest('tr').data('id'));
});
this.$element.delegate('.treeview-tr', 'click', function () {
this.$el.delegate('.treeview-tr', 'click', function () {
var $this = $(this),
record_id = $this.data('id'),
bool = $this.parent().hasClass('oe_open');
@ -220,7 +220,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
render_data: function(groupby){
var self = this;
_.each(_.keys(groupby),function(key){
var $curr_node = self.$element.find('#treerow_' + key);
var $curr_node = self.$el.find('#treerow_' + key);
var record = groupby[key];
var children_rows = QWeb.render('TreeView.rows', {
'records': record,
@ -235,7 +235,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
$curr_node.addClass('oe_open');
$curr_node.after(children_rows);
} else {
self.$element.find('tbody').html(children_rows);
self.$el.find('tbody').html(children_rows);
}
});
self.collpase_all();
@ -272,11 +272,11 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
// show & hide the contents
showcontent: function (record_id, show) {
this.$element.find('#treerow_' + record_id)
this.$el.find('#treerow_' + record_id)
.toggleClass('oe_open', show);
_(this.records[record_id][this.children_field]).each(function (child_id) {
var $child_row = this.$element.find('#treerow_' + child_id);
var $child_row = this.$el.find('#treerow_' + child_id);
if ($child_row.hasClass('oe_open')) {
this.showcontent(child_id, false);
}

View File

@ -14,10 +14,13 @@ instance.web.ActionManager = instance.web.Widget.extend({
this.dialog = null;
this.dialog_widget = null;
this.breadcrumbs = [];
this.on('history_back', this, function() {
return this.history_back();
});
},
start: function() {
this._super.apply(this, arguments);
this.$element.on('click', '.oe_breadcrumb_item', this.on_breadcrumb_clicked);
this.$el.on('click', '.oe_breadcrumb_item', this.on_breadcrumb_clicked);
},
dialog_stop: function () {
if (this.dialog) {
@ -50,10 +53,10 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
var item = _.extend({
show: function(index) {
this.widget.$element.show();
this.widget.$el.show();
},
hide: function() {
this.widget.$element.hide();
this.widget.$el.hide();
},
destroy: function() {
this.widget.destroy();
@ -65,21 +68,46 @@ instance.web.ActionManager = instance.web.Widget.extend({
item.id = _.uniqueId('breadcrumb_');
this.breadcrumbs.push(item);
},
history_back: function() {
var last = this.breadcrumbs.slice(-1)[0];
if (!last) {
return false;
}
var title = last.get_title();
if (_.isArray(title) && title.length > 1) {
return this.select_breadcrumb(this.breadcrumbs.length - 1, title.length - 2);
} else if (this.breadcrumbs.length === 1) {
// Only one single titled item in breadcrumb, most of the time you want to trigger back to home
return false;
} else {
var prev = this.breadcrumbs[this.breadcrumbs.length - 2];
title = prev.get_title();
return this.select_breadcrumb(this.breadcrumbs.length - 2, _.isArray(title) ? title.length - 1 : undefined);
}
},
on_breadcrumb_clicked: function(ev) {
var $e = $(ev.target);
var id = $e.data('id');
var item;
var index;
for (var i = this.breadcrumbs.length - 1; i >= 0; i--) {
var it = this.breadcrumbs[i];
if (it.id == id) {
item = it;
if (this.breadcrumbs[i].id == id) {
index = i;
break;
}
this.remove_breadcrumb(i);
}
var index = $e.parent().find('.oe_breadcrumb_item[data-id=' + $e.data('id') + ']').index($e);
item.show(index, $e);
var subindex = $e.parent().find('.oe_breadcrumb_item[data-id=' + $e.data('id') + ']').index($e);
this.select_breadcrumb(index, subindex);
},
select_breadcrumb: function(index, subindex) {
for (var i = this.breadcrumbs.length - 1; i >= 0; i--) {
if (i > index) {
this.remove_breadcrumb(i);
}
}
var item = this.breadcrumbs[index];
item.show(subindex);
this.inner_widget = item.widget;
return true;
},
clear_breadcrumbs: function() {
while (this.breadcrumbs.length) {
@ -117,28 +145,42 @@ instance.web.ActionManager = instance.web.Widget.extend({
return titles.join(' <span class="oe_fade">/</span> ');
},
do_push_state: function(state) {
state = state || {};
if (this.getParent() && this.getParent().do_push_state) {
if (this.inner_action) {
state['title'] = this.inner_action.name;
state['model'] = this.inner_action.res_model;
if(this.inner_action.type == 'ir.actions.act_window') {
state['model'] = this.inner_action.res_model;
}
if (this.inner_action.id) {
state['action_id'] = this.inner_action.id;
state['action'] = this.inner_action.id;
} else if (this.inner_action.type == 'ir.actions.client') {
state['action'] = this.inner_action.tag;
//state = _.extend(this.inner_action.params || {}, state);
}
}
this.getParent().do_push_state(state);
if(!this.dialog) {
this.getParent().do_push_state(state);
}
}
},
do_load_state: function(state, warm) {
var self = this,
action_loaded;
if (state.action_id) {
var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action_id;
if (run_action) {
if (state.action) {
if (_.isString(state.action) && instance.web.client_actions.contains(state.action)) {
var action_client = {type: "ir.actions.client", tag: state.action, params: state};
this.null_action();
action_loaded = this.do_action(state.action_id);
instance.webclient.menu.has_been_loaded.then(function() {
instance.webclient.menu.open_action(state.action_id);
});
action_loaded = this.do_action(action_client);
} else {
var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action;
if (run_action) {
this.null_action();
action_loaded = this.do_action(state.action);
instance.webclient.menu.has_been_loaded.then(function() {
instance.webclient.menu.open_action(state.action);
});
}
}
} else if (state.model && state.id) {
// TODO handle context & domain ?
@ -152,23 +194,12 @@ instance.web.ActionManager = instance.web.Widget.extend({
action_loaded = this.do_action(action);
} else if (state.sa) {
// load session action
var self = this;
this.null_action();
action_loaded = this.rpc('/web/session/get_session_action', {key: state.sa}).pipe(function(action) {
if (action) {
return self.do_action(action);
}
});
} else if (state.client_action) {
this.null_action();
var action = state.client_action;
if(_.isString(action)) {
action = {
tag: action,
params: state,
};
}
this.ir_actions_client(action);
}
$.when(action_loaded || null).then(function() {
@ -178,7 +209,10 @@ instance.web.ActionManager = instance.web.Widget.extend({
});
},
do_action: function(action, on_close) {
if (_.isNumber(action) || _.isString(action)) {
if (_.isString(action) && instance.web.client_actions.contains(action)) {
var action_client = { type: "ir.actions.client", tag: action };
return this.do_action(action_client);
} else if (_.isNumber(action) || _.isString(action)) {
var self = this;
return self.rpc("/web/action/load", { action_id: action }, function(result) {
self.do_action(result.result, on_close);
@ -186,11 +220,11 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
if (!action.type) {
console.error("No type for action", action);
return;
return null;
}
var type = action.type.replace(/\./g,'_');
var popup = action.target === 'new';
var inline = action.target === 'inline';
var inline = action.target === 'inline' || action.target === 'inlineview';
action.flags = _.extend({
views_switcher : !popup && !inline,
search_view : !popup && !inline,
@ -201,7 +235,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
}, action.flags || {});
if (!(type in this)) {
console.error("Action manager can't handle action of type " + action.type, action);
return;
return null;
}
return this[type](action, on_close);
},
@ -209,21 +243,32 @@ instance.web.ActionManager = instance.web.Widget.extend({
this.dialog_stop();
this.clear_breadcrumbs();
},
ir_actions_act_window: function (action, on_close) {
var self = this;
if (_(['base.module.upgrade', 'base.setup.installer'])
.contains(action.res_model)) {
var old_close = on_close;
on_close = function () {
instance.webclient.do_reload().then(old_close);
ir_actions_common: function(action, on_close) {
var self = this, klass, widget, post_process;
if (action.type === 'ir.actions.client') {
var ClientWidget = instance.web.client_actions.get_object(action.tag);
widget = new ClientWidget(this, action.params);
klass = 'oe_act_client';
post_process = function() {
self.push_breadcrumb({
widget: widget,
title: action.name
});
if (action.tag !== 'reload') {
self.do_push_state({});
}
};
} else {
widget = new instance.web.ViewManagerAction(this, action);
klass = 'oe_act_window';
post_process = widget.proxy('add_breadcrumb');
}
if (action.target === 'new') {
if (this.dialog === null) {
// These buttons will be overwrited by <footer> if any
this.dialog = new instance.web.Dialog(this, {
buttons: { "Close": function() { $(this).dialog("close"); }},
dialogClass: 'oe_act_window'
dialogClass: klass
});
if(on_close)
this.dialog.on_close.add(on_close);
@ -231,21 +276,31 @@ instance.web.ActionManager = instance.web.Widget.extend({
this.dialog_widget.destroy();
}
this.dialog.dialog_title = action.name;
this.dialog_widget = new instance.web.ViewManagerAction(this, action);
this.dialog_widget.appendTo(this.dialog.$element);
this.dialog_widget = widget;
this.dialog_widget.appendTo(this.dialog.$el);
this.dialog.open();
} else {
this.dialog_stop();
this.inner_action = action;
this.inner_widget = widget;
post_process();
this.inner_widget.appendTo(this.$el);
}
},
ir_actions_act_window: function (action, on_close) {
var self = this;
if (action.target !== 'new') {
if(action.menu_id) {
this.dialog_stop();
return this.getParent().do_action(action, function () {
instance.webclient.menu.open_menu(action.menu_id);
});
}
this.inner_action = action;
var inner_widget = this.inner_widget = new instance.web.ViewManagerAction(this, action);
inner_widget.add_breadcrumb();
this.inner_widget.appendTo(this.$element);
}
return this.ir_actions_common(action, on_close);
},
ir_actions_client: function (action, on_close) {
return this.ir_actions_common(action, on_close);
},
ir_actions_act_window_close: function (action, on_closed) {
if (!this.dialog && on_closed) {
@ -262,19 +317,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
self.do_action(action, on_closed)
});
},
ir_actions_client: function (action) {
this.dialog_stop();
var ClientWidget = instance.web.client_actions.get_object(action.tag);
this.inner_widget = new ClientWidget(this, action.params);
this.push_breadcrumb({
widget: this.inner_widget,
title: action.name
});
this.inner_widget.appendTo(this.$element);
},
ir_actions_report_xml: function(action, on_closed) {
var self = this;
$.blockUI();
instance.web.blockUI();
self.rpc("/web/session/eval_domain_and_context", {
contexts: [action.context],
domains: []
@ -284,7 +329,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
self.session.get_file({
url: '/web/report',
data: {action: JSON.stringify(action)},
complete: $.unblockUI,
complete: instance.web.unblockUI,
success: function(){
if (!self.dialog && on_closed) {
on_closed();
@ -298,9 +343,6 @@ instance.web.ActionManager = instance.web.Widget.extend({
ir_actions_act_url: function (action) {
window.open(action.url, action.target === 'self' ? '_self' : '_blank');
},
ir_ui_menu: function (action) {
this.getParent().do_action(action);
}
});
instance.web.ViewManager = instance.web.Widget.extend({
@ -334,7 +376,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
start: function() {
this._super();
var self = this;
this.$element.find('.oe_view_manager_switch a').click(function() {
this.$el.find('.oe_view_manager_switch a').click(function() {
self.on_mode_switch($(this).data('view-type'));
}).tipsy();
var views_ids = {};
@ -343,9 +385,9 @@ instance.web.ViewManager = instance.web.Widget.extend({
deferred : $.Deferred(),
controller : null,
options : _.extend({
$buttons : self.$element.find('.oe_view_manager_buttons'),
$sidebar : self.flags.sidebar ? self.$element.find('.oe_view_manager_sidebar') : undefined,
$pager : self.$element.find('.oe_view_manager_pager'),
$buttons : self.$el.find('.oe_view_manager_buttons'),
$sidebar : self.flags.sidebar ? self.$el.find('.oe_view_manager_sidebar') : undefined,
$pager : self.$el.find('.oe_view_manager_pager'),
action : self.action,
action_views_ids : views_ids
}, self.flags, self.flags[view.view_type] || {}, view.options || {})
@ -353,7 +395,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
views_ids[view.view_type] = view.view_id;
});
if (this.flags.views_switcher === false) {
this.$element.find('.oe_view_manager_switch').hide();
this.$el.find('.oe_view_manager_switch').hide();
}
// If no default view defined, switch to the first one in sequence
var default_view = this.flags.default_view || this.views_src[0].view_type;
@ -390,17 +432,17 @@ instance.web.ViewManager = instance.web.Widget.extend({
this.searchview[(view.controller.searchable === false || this.searchview.hidden) ? 'hide' : 'show']();
}
this.$element
this.$el
.find('.oe_view_manager_switch a').parent().removeClass('active');
this.$element
this.$el
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
.parent().addClass('active');
$.when(view_promise).then(function () {
return $.when(view_promise).then(function () {
_.each(_.keys(self.views), function(view_name) {
var controller = self.views[view_name].controller;
if (controller) {
var container = self.$element.find(".oe_view_manager_view_" + view_name + ":first");
var container = self.$el.find(".oe_view_manager_view_" + view_name + ":first");
if (view_name === view_type) {
container.show();
controller.do_show(view_options || {});
@ -408,33 +450,34 @@ instance.web.ViewManager = instance.web.Widget.extend({
container.hide();
controller.do_hide();
}
// put the <footer> in the dialog's buttonpane
if (self.$element.parent('.ui-dialog-content') && self.$element.find('footer')) {
self.$element.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonset').hide()
self.$element.find('footer').appendTo(
self.$element.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonpane')
// put the <footer> in the dialog's buttonpane
if (self.$el.parent('.ui-dialog-content') && self.$el.find('footer')) {
self.$el.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonset').hide()
self.$el.find('footer').appendTo(
self.$el.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonpane')
);
}
}
});
});
return view_promise;
},
do_create_view: function(view_type) {
// Lazy loading of views
var self = this;
var view = this.views[view_type];
var controllerclass = this.registry.get_object(view_type);
var viewclass = this.registry.get_object(view_type);
var options = _.clone(view.options);
if (view_type === "form" && this.action) {
switch (this.action.target) {
case 'new':
case 'inline':
options.initial_mode = 'edit';
break;
}
if (view_type === "form" && this.action && (this.action.target == 'new' || this.action.target == 'inline')) {
options.initial_mode = 'edit';
}
var controller = new controllerclass(this, this.dataset, view.view_id, options);
var controller = new viewclass(this, this.dataset, view.view_id, options);
controller.on('history_back', this, function() {
var am = self.getParent();
if (am && am.trigger) {
return am.trigger('history_back');
}
});
controller.on("change:title", this, function() {
if (self.active_view === view_type) {
@ -448,7 +491,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
controller.do_switch_view.add_last(_.bind(this.switch_view, this));
controller.do_prev_view.add_last(this.on_prev_view);
var container = this.$element.find(".oe_view_manager_view_" + view_type);
var container = this.$el.find(".oe_view_manager_view_" + view_type);
var view_promise = controller.appendTo(container);
this.views[view_type].controller = controller;
this.views[view_type].deferred.resolve(view_type);
@ -462,7 +505,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
});
},
set_title: function(title) {
this.$element.find('.oe_view_title_text:first').text(title);
this.$el.find('.oe_view_title_text:first').text(title);
},
add_breadcrumb: function() {
var self = this;
@ -478,17 +521,36 @@ instance.web.ViewManager = instance.web.Widget.extend({
});
this.getParent().push_breadcrumb({
widget: this,
show: function(index, $e) {
action: this.action,
show: function(index) {
var view_to_select = views[index];
self.$element.show();
self.$el.show();
if (self.active_view !== view_to_select) {
self.on_mode_switch(view_to_select);
}
},
get_title: function() {
return _.map(views, function(v) {
return self.views[v].controller.get('title');
var id;
var currentIndex;
_.each(self.getParent().breadcrumbs, function(bc, i) {
if (bc.widget === self) {
currentIndex = i;
}
});
var next = self.getParent().breadcrumbs.slice(currentIndex + 1)[0];
var titles = _.map(views, function(v) {
var controller = self.views[v].controller;
if (v === 'form') {
id = controller.datarecord.id;
}
return controller.get('title');
});
if (next && next.action && next.action.res_id && self.active_view === 'form' && self.model === next.action.res_model && id === next.action.res_id) {
// If the current active view is a formview and the next item in the breadcrumbs
// is an action on same object (model / res_id), then we omit the current formview's title
titles.pop();
}
return titles;
}
});
},
@ -538,7 +600,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
this.searchview = new instance.web.SearchView(this, this.dataset, view_id, search_defaults, this.flags.search_view === false);
this.searchview.on_search.add(this.do_searchview_search);
return this.searchview.appendTo(this.$element.find(".oe_view_manager_view_search"));
return this.searchview.appendTo(this.$el.find(".oe_view_manager_view_search"));
},
do_searchview_search: function(domains, contexts, groupbys) {
var self = this,
@ -655,8 +717,8 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
var manager_ready = $.when(searchview_loaded, main_view_loaded);
this.$element.find('.oe_debug_view').change(this.on_debug_changed);
this.$element.addClass("oe_view_manager_" + (this.action.target || 'current'));
this.$el.find('.oe_debug_view').change(this.on_debug_changed);
this.$el.addClass("oe_view_manager_" + (this.action.target || 'current'));
return manager_ready;
},
on_debug_changed: function (evt) {
@ -668,7 +730,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
switch (val) {
case 'fvg':
var dialog = new instance.web.Dialog(this, { title: _t("Fields View Get"), width: '95%' }).open();
$('<pre>').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$element);
$('<pre>').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$el);
break;
case 'perm_read':
var ids = current_view.get_selected_ids();
@ -710,15 +772,6 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
width: '95%'}, $root).open();
});
break;
case 'manage_views':
if (current_view.fields_view && current_view.fields_view.arch) {
var view_editor = new instance.web.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch);
view_editor.start();
} else {
this.do_warn(_t("Manage Views"),
_t("Could not find current view declaration"));
}
break;
case 'edit_workflow':
return this.do_action({
res_model : 'workflow',
@ -775,7 +828,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
var controller = self.views[self.active_view].controller,
fvg = controller.fields_view,
view_id = (fvg && fvg.view_id) || '--';
self.$element.find('.oe_debug_view').html(QWeb.render('ViewManagerDebug', {
self.$el.find('.oe_debug_view').html(QWeb.render('ViewManagerDebug', {
view: controller,
view_manager: self
}));
@ -789,7 +842,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
return r;
},
set_title: function(title) {
this.$element.find('.oe_breadcrumb_title:first').html(this.getParent().get_title());
this.$el.find('.oe_breadcrumb_title:first').html(this.getParent().get_title());
},
do_push_state: function(state) {
if (this.getParent() && this.getParent().do_push_state) {
@ -821,7 +874,7 @@ instance.web.Sidebar = instance.web.Widget.extend({
var view = this.getParent();
this.sections = [
{ 'name' : 'print', 'label' : _t('Print'), },
{ 'name' : 'files', 'label' : _t('Attachment'), },
{ 'name' : 'files', 'label' : _t('Attachment(s)'), },
{ 'name' : 'other', 'label' : _t('More'), }
];
this.items = {
@ -841,14 +894,14 @@ instance.web.Sidebar = instance.web.Widget.extend({
} else {
self.do_attachement_update(self.dataset, self.model_id);
}
$.unblockUI();
instance.web.unblockUI();
});
},
start: function() {
var self = this;
this._super(this);
this.redraw();
this.$element.on('click','.oe_dropdown_menu li a', function(event) {
this.$el.on('click','.oe_dropdown_menu li a', function(event) {
var section = $(this).data('section');
var index = $(this).data('index');
var item = self.items[section][index];
@ -864,7 +917,7 @@ instance.web.Sidebar = instance.web.Widget.extend({
},
redraw: function() {
var self = this;
self.$element.html(QWeb.render('Sidebar', {widget: self}));
self.$el.html(QWeb.render('Sidebar', {widget: self}));
// Hides Sidebar sections when item list is empty
this.$('.oe_form_dropdown_section').each(function() {
@ -971,16 +1024,16 @@ instance.web.Sidebar = instance.web.Widget.extend({
self.items['files'] = attachments;
self.redraw();
this.$('.oe_sidebar_add_attachment .oe_form_binary_file').change(this.on_attachment_changed);
this.$element.find('.oe_sidebar_delete_item').click(this.on_attachment_delete);
this.$el.find('.oe_sidebar_delete_item').click(this.on_attachment_delete);
},
on_attachment_changed: function(e) {
var $e = $(e.target);
if ($e.val() !== '') {
this.$element.find('form.oe_form_binary_form').submit();
this.$el.find('form.oe_form_binary_form').submit();
$e.parent().find('input[type=file]').prop('disabled', true);
$e.parent().find('button').prop('disabled', true).find('img, span').toggle();
this.$('.oe_sidebar_add_attachment span').text(_t('Uploading...'));
$.blockUI();
instance.web.blockUI();
}
},
on_attachment_delete: function(e) {
@ -1007,14 +1060,15 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
this['on_button_' + _t("Close")] = this.on_btn_close;
this._super(view, {
width: '80%',
height: '80%'
height: '80%',
destroy_on_close: false,
});
this.view = view;
this.view_type = view.fields_view.type || '';
this.$fields_form = null;
this.$view_form = null;
this.$sidebar_form = null;
this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name });
this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name; });
this.languages = null;
this.languages_loaded = $.Deferred();
(new instance.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(),
@ -1024,8 +1078,8 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
var self = this;
this._super();
$.when(this.languages_loaded).then(function() {
self.$element.html(instance.web.qweb.render('TranslateDialog', { widget: self }));
self.$fields_form = self.$element.find('.oe_translation_form');
self.$el.html(instance.web.qweb.render('TranslateDialog', { widget: self }));
self.$fields_form = self.$el.find('.oe_translation_form');
self.$fields_form.find('.oe_trad_field').change(function() {
$(this).toggleClass('touched', ($(this).val() != $(this).attr('data-value')));
});
@ -1045,7 +1099,8 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
deffered.push(deff);
var callback = function(values) {
_.each(self.translatable_fields_keys, function(f) {
self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(values[0][f] || '').attr('data-value', values[0][f] || '');
var value = values[0][f] || '';
self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(value).attr('data-value', value);
});
deff.resolve();
};
@ -1056,27 +1111,22 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
});
callback([values]);
} else {
self.rpc('/web/dataset/get', {
model: self.view.dataset.model,
ids: [self.view.datarecord.id],
fields: self.translatable_fields_keys,
context: self.view.dataset.get_context({
'lang': lg.code
})}, callback);
self.view.dataset.read_ids([self.view.datarecord.id], self.translatable_fields_keys, {
context: { 'lang': lg.code }
}).then(callback);
}
});
$.when.apply(null, deffered).then(callback);
},
open: function(field) {
var self = this,
sup = this._super;
var self = this;
this._super();
$.when(this.languages_loaded).then(function() {
if (self.view.translatable_fields && self.view.translatable_fields.length) {
self.do_load_fields_values(function() {
sup.call(self);
if (field) {
var $field_input = self.$element.find('tr[data-field="' + field.name + '"] td:nth-child(2) *:first-child');
self.$element.scrollTo($field_input);
var $field_input = self.$el.find('tr[data-field="' + field.name + '"] td:nth-child(2) *:first-child');
self.$el.scrollTo($field_input);
$field_input.focus();
}
});
@ -1114,7 +1164,6 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
});
instance.web.View = instance.web.Widget.extend({
template: "EmptyComponent",
// name displayed in view switchers
display_name: '',
/**
@ -1127,7 +1176,7 @@ instance.web.View = instance.web.Widget.extend({
this.view_id = view_id;
this.set_default_options(options);
},
start: function() {
start: function () {
return this.load_view();
},
load_view: function() {
@ -1167,7 +1216,7 @@ instance.web.View = instance.web.Widget.extend({
},
open_translate_dialog: function(field) {
if (!this.translate_dialog) {
this.translate_dialog = new instance.web.TranslateDialog(this).start();
this.translate_dialog = new instance.web.TranslateDialog(this);
}
this.translate_dialog.open(field);
},
@ -1253,10 +1302,10 @@ instance.web.View = instance.web.Widget.extend({
this.embedded_view = embedded_view;
},
do_show: function () {
this.$element.show();
this.$el.show();
},
do_hide: function () {
this.$element.hide();
this.$el.hide();
},
do_push_state: function(state) {
if (this.getParent() && this.getParent().do_push_state) {
@ -1284,12 +1333,10 @@ instance.web.View = instance.web.Widget.extend({
do_search: function(view) {
},
on_sidebar_import: function() {
var import_view = new instance.web.DataImport(this, this.dataset);
import_view.start();
new instance.web.DataImport(this, this.dataset).open();
},
on_sidebar_export: function() {
var export_view = new instance.web.DataExport(this, this.dataset);
export_view.start();
new instance.web.DataExport(this, this.dataset).open();
},
on_sidebar_translate: function() {
return this.do_action({
@ -1337,7 +1384,6 @@ instance.web.xml_to_json = function(node) {
}
instance.web.json_node_to_xml = function(node, human_readable, indent) {
// For debugging purpose, this function will convert a json node back to xml
// Maybe useful for xml view editor
indent = indent || 0;
var sindent = (human_readable ? (new Array(indent + 1).join('\t')) : ''),
r = sindent + '<' + node.tag,
@ -1346,7 +1392,9 @@ instance.web.json_node_to_xml = function(node, human_readable, indent) {
if (typeof(node) === 'string') {
return sindent + node;
} else if (typeof(node.tag) !== 'string' || !node.children instanceof Array || !node.attrs instanceof Object) {
throw("Node a json node");
throw new Error(
_.str.sprintf("Node [%s] is not a JSONified XML node",
JSON.stringify(node)));
}
for (var attr in node.attrs) {
var vattr = node.attrs[attr];

View File

@ -25,6 +25,13 @@
</div>
</div>
</t>
<t t-name="Tipsy.alert">
<a class="oe_tooltip_close oe_e">[</a>
<span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert ui-state-error"></span>
<div class="oe_tooltip_message">
<t t-esc="message"/>
</div>
</t>
<t t-name="CrashManager.warning">
<table cellspacing="0" cellpadding="0" border="0" class="oe_dialog_warning">
@ -44,29 +51,6 @@
</table>
</t>
<t t-name="CrashManager.error">
<t t-if="!session.openerp_entreprise">
<span>Your version of OpenERP is unsupported. Support &amp; maintenance services are available here: <a href="http://www.openerp.com/support-or-publisher-warranty-contract" target="_blank">OpenERP Entreprise</a>.</span>
</t>
<t t-if="session.openerp_entreprise">
<div class="oe_error_send">
<div>
<div class="oe_centeralign"><b>OpenERP Enterprise Contract.</b></div>
<div><br/>Your report will be sent to the OpenERP Enterprise team.<br/></div><br/>
<div>
<label>Summary:</label><br/>
<input id="issuename" type="text" class="oe_fielddiv"/>
</div><br/><br/>
<div>
<label>Description:</label><br/>
<textarea id="explanation" rows="6"></textarea>
</div><br/><br/>
<div>
<label>What you did:</label><br/>
<textarea id="remark" rows="6" ></textarea>
</div>
</div>
</div><br/>
</t>
<div class="oe_error_detail">
<pre><t t-esc="error.message"/></pre>
<hr/>
@ -114,25 +98,25 @@
</select>
</t>
<t t-name="DatabaseManager">
<div class="oe_database_manager">
<div class="oe_database_manager_menu">
<ul class="oe_notebook">
<li><a href="#db_create">Create</a></li>
<li><a href="#db_drop">Drop</a></li>
<li><a href="#db_backup">Backup</a></li>
<li><a href="#db_restore">Restore</a></li>
<li><a href="#db_change_password">Password</a></li>
<li><a id="back_to_login" href="#">Back to Login</a></li>
</ul>
<form id="db_create" name="create_db_form" method="POST">
<div class="oe_view_manager_view_form">
<div class="oe_form">
<form id="db_create" name="create_db_form" style="display: block;">
<div class="oe_view_manager oe_view_manager_current">
<div class="oe_view_manager_header" style="padding: 8px;">
<div class="oe_header_row">
<h2 class="oe_view_title">
<span class="oe_view_title_text oe_breadcrumb_title">Create Database</span>
</h2>
<button type="submit" class="oe_button oe_highlight db_create">Create</button>
</div>
</div>
</div>
<table align="center" class="db_option_table">
<tr>
<th colspan="2" class="option_string"> CREATE DATABASE </th>
</tr>
<tr>
<td><label for="super_admin_pwd">Master password:</label></td>
<td><input type="password" name="super_admin_pwd" class="required" value="admin"/></td>
<td><input type="password" name="super_admin_pwd" class="required" value="admin" /></td>
</tr>
<tr>
<td><label for="db_name">New database name:</label></td>
@ -140,42 +124,54 @@
</tr>
<tr>
<td><label for="demo_data">Load Demonstration data:</label></td>
<td><input type="checkbox" name="demo_data"/></td>
<td class="oe_form_group_cell">
<span class="oe_form_field oe_form_field_boolean">
<input type="checkbox" name="demo_data" />
</span>
</td>
</tr>
<tr>
<td><label for="db_lang">Default language:</label></td>
<td>
<td class="oe_form_field oe_form_field_selection ">
<select name="db_lang" t-if="widget.lang_list">
<t t-foreach="widget.lang_list" t-as="lang">
<option t-att-value="lang[0]" t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined"><t t-esc="lang[1]"/></option>
<option t-att-value="lang[0]" t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined">
<t t-esc="lang[1]" />
</option>
</t>
</select>
</td>
</tr>
<tr>
<td><label for="create_admin_pwd">Admin password:</label></td>
<td><input type="password" name="create_admin_pwd" class="required"/></td>
<td><input type="password" name="create_admin_pwd" class="required" /></td>
</tr>
<tr>
<td><label for="create_confirm_pwd">Confirm password:</label></td>
<td><input type="password" name="create_confirm_pwd" class="required" equalTo="input[name=create_admin_pwd]"/></td>
</tr>
<tr>
<td colspan="2" align="right"><button class="oe_button">Create</button></td>
</tr>
</table>
</form>
<form id="db_drop" name="drop_db_form" method="POST">
<form id="db_drop" name="drop_db_form" style="display: none; ">
<div class="oe_view_manager oe_view_manager_current">
<div class="oe_view_manager_header" style="padding: 8px;">
<div class="oe_header_row">
<h2 class="oe_view_title">
<span class="oe_view_title_text oe_breadcrumb_title">Drop Database</span>
</h2>
<button type="submit" class="oe_button oe_highlight db_drop">Drop</button>
</div>
</div>
</div>
<table align="center" class="db_option_table">
<tr>
<th colspan="2" class="option_string"> DROP DATABASE </th>
</tr>
<tr>
<td><label for="drop_db">Database:</label></td>
<td>
<td class="oe_form_field oe_form_field_selection">
<select t-if="widget.db_list" name="drop_db" autofocus="autofocus">
<t t-foreach="widget.db_list" t-as="db">
<option t-att-value="db"><t t-esc="db"/></option>
<option t-att-value="db">
<t t-esc="db" />
</option>
</t>
</select>
<input t-if="!widget.db_list" name="drop_db" class="required" type="text" autofocus="autofocus"/>
@ -183,25 +179,31 @@
</tr>
<tr>
<td><label for="drop_password">Master Password:</label></td>
<td><input type="password" name="drop_pwd" class="required"/></td>
</tr>
<tr>
<td colspan="2" align="right"><button class="oe_button">Drop</button></td>
<td><input type="password" name="drop_pwd" class="required" /></td>
</tr>
</table>
</form>
<form id="db_backup" name="backup_db_form" method="POST" target="backup-target" action="/web/database/backup">
<input type="hidden" name="token"/>
<form id="db_backup" name="backup_db_form" target="backup-target" action="/web/database/backup" style="display: none;">
<div class="oe_view_manager oe_view_manager_current">
<div class="oe_view_manager_header" style="padding: 8px;">
<div class="oe_header_row">
<h2 class="oe_view_title">
<span class="oe_view_title_text oe_breadcrumb_title">Backup Database</span>
</h2>
<button type="submit" class="oe_button oe_highlight db_backup">Backup</button>
</div>
</div>
</div>
<input type="hidden" name="token" />
<table align="center" class="db_option_table">
<tr>
<th colspan="2" class="option_string"> BACKUP DATABASE </th>
</tr>
<tr>
<td><label for="backup_db">Database:</label></td>
<td>
<td class="oe_form_field oe_form_field_selection ">
<select t-if="widget.db_list" name="backup_db" autofocus="autofocus">
<t t-foreach="widget.db_list" t-as="db">
<option t-att-value="db"><t t-esc="db"/></option>
<option t-att-value="db">
<t t-esc="db" />
</option>
</t>
</select>
<input t-if="!widget.db_list" name="backup_db" class="required" type="text" autofocus="autofocus"/>
@ -209,18 +211,22 @@
</tr>
<tr>
<td><label for="backup_pwd">Master Password:</label></td>
<td><input type="password" name="backup_pwd" class="required"/></td>
</tr>
<tr>
<td colspan="2" align="right"><button class="oe_button">Backup</button></td>
<td><input type="password" name="backup_pwd" class="required" /></td>
</tr>
</table>
</form>
<form id="db_restore" name="restore_db_form" method="POST">
<form id="db_restore" name="restore_db_form" style="display: none; ">
<div class="oe_view_manager oe_view_manager_current">
<div class="oe_view_manager_header" style="padding: 8px;">
<div class="oe_header_row">
<h2 class="oe_view_title">
<span class="oe_view_title_text oe_breadcrumb_title">Restore Database</span>
</h2>
<button type="submit" class="oe_button oe_highlight db_restore">Restore</button>
</div>
</div>
</div>
<table align="center" class="db_option_table">
<tr>
<th colspan="2" class="option_string"> RESTORE DATABASE </th>
</tr>
<tr>
<td><label for="restore_db">File:</label></td>
<td><input type="file" name="db_file" class="required" autofocus="autofocus"/></td>
@ -233,16 +239,20 @@
<td><label for="new_db">New database name:</label></td>
<td><input type="text" name="new_db" class="required"/></td>
</tr>
<tr>
<td colspan="2" align="right"><button class="oe_button">Restore</button></td>
</tr>
</table>
</form>
<form id="db_change_password" name="change_pwd_form" method="POST">
<form id="db_change_password" name="change_pwd_form" style="display: none;">
<div class="oe_view_manager oe_view_manager_current">
<div class="oe_view_manager_header" style="padding: 8px;">
<div class="oe_header_row">
<h2 class="oe_view_title">
<span class="oe_view_title_text oe_breadcrumb_title">Change Master Password</span>
</h2>
<button type="submit" class="oe_button oe_highlight db-change-password">Change Password</button>
</div>
</div>
</div>
<table align="center" class="db_option_table">
<tr>
<th colspan="2" class="option_string"> CHANGE MASTER PASSWORD </th>
</tr>
<tr>
<td><label for="old_pwd">Master password:</label></td>
<td><input type="password" name="old_pwd" class="required" minlength="1" autofocus="autofocus"/></td>
@ -253,16 +263,53 @@
</tr>
<tr>
<td><label for="confirm_pwd">Confirm new master password:</label></td>
<td><input type="password" name="confirm_pwd" class="required" equalTo="input[name=new_pwd]" minlength="1"/></td>
</tr>
<tr>
<td colspan="2" align="right"><button class="oe_button">Change Password</button></td>
<td><input type="password" name="confirm_pwd" class="required" equalTo="input[name=new_pwd]" minlength="1"/> </td>
</tr>
</table>
</form>
</div>
</div>
</t>
<t t-name="DatabaseManager.user_menu">
<span class="oe_right">
<a id="back-to-login" href="#"><span class="oe_topbar_item oe_topbar_name">Back to Login</span></a>
</span>
</t>
<t t-name="DatabaseManager.menu">
<div class="oe_secondary_menu_section">Database Management</div>
<ul class="oe_secondary_submenu">
<li><a href="#db_create">Create</a></li>
<li><a href="#db_drop">Drop</a></li>
<li><a href="#db_backup">Backup</a></li>
<li><a href="#db_restore">Restore</a></li>
<li><a href="#db_change_password">Password</a></li>
</ul>
</t>
<t t-name="ChangePassword">
<form name="change_password_form" method="POST">
<table align="center">
<tr>
<td><label for="old_pwd">Old Password:</label></td>
<td><input type="password" name="old_pwd"
minlength="1" autofocus="autofocus"/></td>
</tr>
<tr>
<td><label for="new_password">New Password:</label></td>
<td><input type="password" name="new_password"
minlength="1"/></td>
</tr>
<tr>
<td><label for="confirm_pwd">Confirm Password:</label></td>
<td><input type="password" name="confirm_pwd"
minlength="1"/></td>
</tr>
<tr>
<td colspan="2" align="right"><button class="oe_button">Change Password</button></td>
</tr>
</table>
</form>
</t>
<t t-name="Menu">
<ul class="oe_menu" t-if="widget.data">
@ -303,14 +350,14 @@
</ul>
</t>
<t t-name="Menu.secondary.link">
<a t-attf-href="#menu_id=#{menu.id}&amp;action_id=#{menu.action ? menu.action.split(',')[1] : ''}"
<a t-attf-href="#menu_id=#{menu.id}&amp;action=#{menu.action ? menu.action.split(',')[1] : ''}"
t-att-class="menu.children.length ? 'oe_menu_toggler' : 'oe_menu_leaf'"
t-att-data-menu="menu.id"
t-att-data-action-model="menu.action ? menu.action.split(',')[0] : ''"
t-att-data-action-id="menu.action ? menu.action.split(',')[1] : ''">
<t t-esc="menu.name"/>
<t t-if="menu.needaction_enabled and menu.needaction_counter">
<div class="oe_menu_counter">
<div class="oe_tag oe_tag_dark oe_menu_counter">
<t t-esc="menu.needaction_counter"/>
</div>
</t>
@ -329,48 +376,24 @@
</span>
</t>
<t t-name="UserMenu.about">
<div>
<a class="oe_activate_debug_mode" href="?debug" style="float:right; font-size: 80%;">Activate the developer mode</a>
<h1 style="margin:0;">OpenERP</h1>
<h3 style="margin:15px 0;padding:0;">Version <t t-esc="version_info.version"/></h3>
<p>
Copyright © 2004-TODAY OpenERP SA. All Rights Reserved.<br />
OpenERP is a trademark of the <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP SA Company</a>.
</p>
<p>
Licenced under the terms of <a target="_blank" href="http://www.gnu.org/licenses/agpl.html" style="text-decoration: underline;">GNU Affero General Public License</a>
</p>
<p>
For more information visit <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP.com</a>
</p>
<div class="oe_about">
<a class="oe_activate_debug_mode oe_right" href="?debug" style="background-color: white; padding:2px 6px; border-radius: 10px;">Activate the developer mode</a>
<img class="oe_logo" src="/web/static/src/img/logo2.png"/>
<h3>Version <t t-esc="version_info.version"/></h3>
<div class="oe_bottom">
<p>Copyright © 2004-TODAY OpenERP SA. All Rights Reserved.<br />
OpenERP is a trademark of the <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP SA Company</a>.</p>
<p>Licenced under the terms of <a target="_blank" href="http://www.gnu.org/licenses/agpl.html" style="text-decoration: underline;">GNU Affero General Public License</a></p>
<p>For more information visit <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP.com</a></p>
</div>
</div>
</t>
<t t-name="UserMenu.password">
<form name="change_password_form" method="POST">
<table align="center">
<tr>
<td><label for="old_pwd">Old Password:</label></td>
<td><input type="password" name="old_pwd"
minlength="1" autofocus="autofocus"/></td>
</tr>
<tr>
<td><label for="new_password">New Password:</label></td>
<td><input type="password" name="new_password"
minlength="1" autofocus="autofocus"/></td>
</tr>
<tr>
<td><label for="confirm_pwd">Confirm Password:</label></td>
<td><input type="password" name="confirm_pwd"
minlength="1"/></td>
</tr>
<tr>
<td colspan="2" align="right"><button class="oe_button">Change Password</button></td>
</tr>
</table>
</form>
</t>
<t t-name="WebClient">
<div class="openerp openerp_webclient_container">
<table class="oe_webclient">
<tr>
<td colspan="2" class="oe_topbar">
@ -394,15 +417,22 @@
</td>
</tr>
</table>
</div>
</t>
<t t-name="EmbedClient">
<div class="openerp">
<div class="oe_application"></div>
</div>
</t>
<t t-name="ViewManager">
<div class="oe_view_manager">
<table class="oe_view_manager_header">
<col width="20%"/>
<col width="25%"/>
<col width="20%"/>
<col width="35%"/>
<col width="20%"/>
<col width="25%"/>
<col width="20%"/>
<col width="35%"/>
<tr class="oe_header_row oe_header_row_top">
<td colspan="2">
<h2 class="oe_view_title" t-if="widget.flags.display_title !== false">
@ -510,26 +540,27 @@
<t t-foreach="widget.sections" t-as="section">
<div class="oe_form_dropdown_section">
<button class="oe_dropdown_toggle oe_dropdown_arrow">
<t t-if="section.name == 'files'" t-raw="widget.items[section.name].length || ''"/>
<t t-esc="section.label"/>
<ul class="oe_dropdown_menu">
<li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
<a class="oe_sidebar_action_a" t-att-title="item.title" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank">
<t t-raw="item.label"/>
</a>
<a t-if="section.name == 'files'" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a>
</li>
<li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_action">/web/binary/upload_attachment</t>
<input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/>
<input type="hidden" name="id" t-att-value="widget.model_id"/>
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
<span>Add...</span>
</t>
</li>
</ul>
</button>
<ul class="oe_dropdown_menu">
<li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
<a class="oe_sidebar_action_a" t-att-title="item.title" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank">
<t t-raw="item.label"/>
</a>
<a t-if="section.name == 'files'" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a>
</li>
<li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_action">/web/binary/upload_attachment</t>
<input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/>
<input type="hidden" name="id" t-att-value="widget.model_id"/>
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
<span>Add...</span>
</t>
</li>
</ul>
</div>
</t>
</div>
@ -600,11 +631,11 @@
</th>
<t t-foreach="columns" t-as="column">
<th t-if="!column.meta and column.invisible !== '1'" t-att-data-id="column.id"
t-att-class="((options.sortable and column.tag !== 'button') ? 'oe_sortable' : null)">
t-attf-class="oe_list_header_#{column.widget or column.type} #{((options.sortable and column.tag !== 'button') ? 'oe_sortable' : null)}">
<t t-if="column.tag !== 'button'"><t t-esc="column.string"/></t>
</th>
</t>
<th t-if="options.deletable" width="1"/>
<th t-if="options.deletable" width="13px"/>
</tr>
</thead>
<tfoot>
@ -622,9 +653,9 @@
<button type="button" class="oe_button oe_list_add oe_highlight">
<t t-esc="widget.options.addable"/>
</button>
<t t-if="widget.options.import_enabled">
<span class="oe_alternative" t-if="widget.options.import_enabled">
<span class="oe_fade">or</span> <a href="#" class="oe_bold oe_list_button_import">Import</a>
</t>
</span>
</t>
</div>
<t t-name="ListView.pager">
@ -638,16 +669,14 @@
<t t-name="ListView.rows" t-foreach="records.length" t-as="index">
<t t-call="ListView.row">
<t t-set="record" t-value="records.at(index)"/>
<t t-set="row_parity" t-value="index_parity"/>
</t>
</t>
<tr t-name="ListView.row" t-att-class="row_parity"
<tr t-name="ListView.row"
t-att-data-id="record.get('id')"
t-att-style="view.style_for(record)">
<t t-set="asData" t-value="record.toForm().data"/>
<t t-foreach="columns" t-as="column">
<td t-if="column.meta">
</td>
<td t-if="column.meta"> </td>
</t>
<th t-if="options.selectable" class="oe_list_record_selector" width="1">
<t t-set="checked" t-value="options.select_view_id == record.get('id') ? 'checked' : null"/>
@ -655,23 +684,34 @@
<input t-if="!options.radio" type="checkbox" name="radiogroup" t-att-checked="checked"/>
</th>
<t t-foreach="columns" t-as="column">
<t t-set="align" t-value="column.type === 'integer' or column.type == 'float'"/>
<t t-set="number" t-value="column.type === 'integer' or column.type == 'float'"/>
<t t-set="modifiers" t-value="column.modifiers_for(asData)"/>
<td t-if="!column.meta and column.invisible !== '1'" t-att-title="column.help"
t-att-class="'oe_list_field_cell' + (align ? ' oe_number' : '')
+ (column.tag === 'button' ? ' oe_button' : '')"
t-att-data-field="column.id">
<t t-raw="render_cell(record, column)"/>
</td>
t-attf-class="oe_list_field_cell oe_list_field_#{column.widget or column.type} #{number ? 'oe_number' : ''} #{column.tag === 'button' ? 'oe-button' : ''} #{modifiers.readonly ? 'oe_readonly' : ''} #{modifiers.required ? 'oe_required' : ''}"
t-att-data-field="column.id"
><t t-raw="render_cell(record, column)"/></td>
</t>
<td t-if="options.deletable" class='oe_list_record_delete' width="1">
<td t-if="options.deletable" class='oe_list_record_delete' width="13px">
<button type="button" name="delete" class="oe_i">d</button>
</td>
</tr>
<t t-name="ListView.row.save">
<td>
<button class='oe_i oe_list_edit_row_save' type='button' name='save'/>
</td>
<t t-extend="ListView.buttons">
<t t-jquery="button.oe_list_add" t-operation="after">
<button class="oe_button oe_list_save oe_highlight"
type="button">Save</button>
</t>
<t t-jquery="a.oe_list_button_import" t-operation="after">
<a href="#" class="oe_bold oe_list_discard">discard</a>
</t>
</t>
<t t-extend="ListView.row">
<!-- adds back padding to row being rendered after edition, if necessary
(if not deletable add back padding), otherwise the row being added is
missing columns
-->
<t t-jquery="&gt; :last" t-operation="after">
<td t-if="edited and !options.deletable" class="oe-listview-padding"/>
</t>
</t>
<t t-name="FormView">
@ -685,7 +725,9 @@
<div t-name="FormView.buttons" class="oe_form_buttons">
<t t-if="widget.options.action_buttons !== false">
<span class="oe_form_buttons_view">
<button type="button" class="oe_button oe_form_button_edit">Edit</button>
<div style="display: inline-block;"> <!-- required for the bounce effect on button -->
<button type="button" class="oe_button oe_form_button_edit">Edit</button>
</div>
<button type="button" class="oe_button oe_form_button_create">Create</button>
</span>
<span class="oe_form_buttons_edit">
@ -905,9 +947,18 @@
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-att-placeholder="! widget.get('effective_readonly') ? widget.node.attrs.placeholder : ''"
></textarea>
<img class="oe_field_translate oe_input_icon" t-if="widget.field.translate"
t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/>
></textarea><img class="oe_field_translate oe_input_icon"
t-if="widget.field.translate and !widget.get('effective_readonly')"
t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"
/>
</div>
</t>
<t t-name="FieldTextHtml">
<div t-att-class="'oe_form_field oe_form_field_html' + (widget.get('effective_readonly') ? ' oe_form_embedded_html' : '')"
t-att-style="widget.node.attrs.style">
<t t-if="! widget.get('effective_readonly')">
<textarea/>
</t>
</div>
</t>
<t t-name="web.datepicker">
@ -943,11 +994,14 @@
</t>
<t t-name="FieldMany2One">
<span class="oe_form_field oe_form_field_many2one oe_form_field_with_button" t-att-style="widget.node.attrs.style">
<a t-if="widget.get('effective_readonly')" href="#" class="oe_form_uri"/>
<t t-if="widget.get('effective_readonly')">
<a t-if="! widget.options.no_open" href="#" class="oe_form_uri"/>
<span t-if="widget.options.no_open" href="#" class="oe_form_uri"/>
<span class="oe_form_m2o_follow"/>
</t>
<t t-if="!widget.get('effective_readonly')">
<button class="oe_button oe_m2o_cm_button" title="Open Resource">
<img t-att-src='_s + "/web/static/src/img/icons/terp-folder-yellow.png"'/>
</button>
<a t-if="! widget.options.no_open" href="#" tabindex="-1"
class="oe_m2o_cm_button oe_e">/</a>
<div>
<input type="text"
t-att-id="widget.id_for_label"
@ -962,7 +1016,6 @@
</t>
</span>
</t>
<!-- Collection of m2m tags -->
<t t-name="FieldMany2ManyTags">
<div class="oe_form_field oe_tags" t-att-style="widget.node.attrs.style">
<t t-if="! widget.get('effective_readonly')">
@ -971,7 +1024,6 @@
</t>
</div>
</t>
<!-- Individual m2m tag element -->
<t t-name="FieldMany2ManyTag">
<t t-set="i" t-value="0"/>
<t t-foreach="elements" t-as="el">
@ -1009,14 +1061,14 @@
</span>
</t>
<t t-name="FieldStatus">
<ul class="oe_form_steps" t-att-style="widget.node.attrs.style"/>
<ul t-att-class="widget.options.clickable ? 'oe_form_steps_clickable' : 'oe_form_steps'" t-att-style="widget.node.attrs.style"/>
</t>
<t t-name="FieldStatus.content">
<t t-set="size" t-value="widget.to_show.length"/>
<t t-foreach="_.range(size)" t-as="i">
<li t-att-class="widget.to_show[i][0] === widget.selected_value ? 'oe_form_steps_active' : ''">
<span><t t-esc="widget.to_show[i][1]"/></span>
<img t-att-src='_s + "/web/static/src/img/form_steps.png"' class="oe_form_steps_arrow" t-if="i &lt; size - 1"/>
<t t-foreach="widget.selection" t-as="i">
<li t-att-class="i[0] === widget.selected_value ? 'oe_active' : ''" t-att-data-id="i[0]">
<span class="label"><t t-esc="i[1]"/></span>
<!-- are you mit ? -->
<span class="arrow"><span></span></span>
</li>
</t>
</t>
@ -1025,13 +1077,8 @@
<div class="oe_form_field_image_controls oe_edit_only">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<button class="oe_button" type="button" title="Set Image">
<img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/>
</button>
Edit
</t>
<button class="oe_button oe_form_binary_file_clear" type="button" title="Clear">
<img t-att-src='_s + "/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"'/>
</button>
<div class="oe_form_binary_progress" style="display: none">
<img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/>
<b>Uploading ...</b>
@ -1119,7 +1166,7 @@
t-att-style="widget.node.attrs.style"
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus">
<img t-if="widget.node.attrs.icon" t-att-src="_s + '/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/>
<img t-if="widget.node.attrs.icon" t-att-src="_s + widget.node.attrs.icon" width="16" height="16"/>
<span t-if="widget.string"><t t-esc="widget.string"/></span>
</button>
</t>
@ -1167,16 +1214,61 @@
</tr>
<tr t-foreach="widget.view.translatable_fields" t-as="field" t-att-data-field="field.name">
<td class="oe_form_group_cell" width="1%" nowrap="nowrap">
<label class="oe_label"><t t-esc="field.node.attrs.string"/>:</label>
<label class="oe_label"><t t-esc="field.string"/>:</label>
</td>
<td t-foreach="widget.languages" t-as="lg" class="oe_form_group_cell">
<input t-if="field.type == 'char'" type="text" t-attf-name="#{lg.code}-#{field.name}" value="" data-value="" class="oe_trad_field" style="width: 100%"/>
<textarea t-if="field.type == 'text'" t-attf-name="#{lg.code}-#{field.name}" data-value="" class="oe_trad_field" style="width: 100%"></textarea>
<input t-if="field.field.type == 'char'" type="text" t-attf-name="#{lg.code}-#{field.name}" value="" data-value="" class="oe_trad_field" style="width: 100%"/>
<textarea t-if="field.field.type == 'text'" t-attf-name="#{lg.code}-#{field.name}" data-value="" class="oe_trad_field" style="width: 100%"></textarea>
</td>
</tr>
</table>
</t>
<t t-name="AbstractFormPopup.render">
<div>
<table style="width:100%">
<tr style="width:100%">
<td style="width:100%">
<div class="oe_popup_search" style="width:100%"></div>
</td>
</tr>
<tr style="width:100%">
<td style="width:100%">
<div class="oe_popup_list" style="width:100%"></div>
</td>
</tr>
</table>
<div class="oe_popup_form" style="width:100%"></div>
</div>
</t>
<t t-name="SelectCreatePopup.search.buttons">
<t t-if="! widget.options.disable_multiple_selection">
<button type="button" class="oe_button oe_selectcreatepopup-search-select" disabled="disabled">Select</button>
or
</t>
<a class="oe_button oe_selectcreatepopup-search-close oe_bold oe_form_button_cancel" href="javascript:void(0)">Cancel</a>
</t>
<t t-name="AbstractFormPopup.buttons">
<t t-if="! readonly">
<t t-if="! multi_select">
<button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save</button>
</t>
<t t-if="multi_select">
<button type="button" class="oe_button oe_abstractformpopup-form-save-new oe_highlight">Save &amp; New</button>
<button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save &amp; Close</button>
</t>
or
</t>
<a class="oe_button oe_abstractformpopup-form-close oe_bold oe_form_button_cancel" href="javascript:void(0)">
<t t-if="! readonly">
Discard
</t>
<t t-if="readonly">
Close
</t>
</a>
</t>
<t t-name="One2Many.viewmanager" t-extend="ViewManager">
<t t-jquery=".oe-view-manager-header">
this.attr('t-if', 'views.length != 1');
@ -1207,6 +1299,8 @@
<div class="oe_searchview_clear"/>
<div class="oe_searchview_unfold_drawer" title="Advanced Search..."/>
<div class="oe_searchview_drawer"/>
<button type="button" class="oe_searchview_search"
title="Search Again">Search</button>
</div>
<div t-name="SearchView.InputView"
@ -1214,7 +1308,7 @@
contenteditable="true"/>
<!-- tabindex: makes div focusable -->
<div t-name="SearchView.FacetView"
class="oe_searchview_facet"
class="oe_tag oe_tag_dark oe_searchview_facet"
tabindex="0"
><span class="oe_facet_remove">x</span
><span class="oe_facet_category oe_i" t-if="widget.model.has('icon')">
@ -1227,7 +1321,6 @@
<span t-name="SearchView.FacetView.Value" class="oe_facet_value">
<t t-esc="widget.model.get('label')"/>
</span>
<t t-name="SearchView.managed-filters">
<option class="oe_search_filters_title" value="">Filters</option>
<optgroup label="-- Filters --">
@ -1252,7 +1345,6 @@
<p>(Any existing filter with the same name will be replaced)</p>
</div>
</t>
<t t-name="SearchView.render_lines">
<table class="oe_search_render_line" border="0" cellspacing="0" cellpadding="0"
t-foreach="lines" t-as="line">
@ -1391,20 +1483,7 @@
<div>
</div>
</div>
<div t-name="SearchView.addtodashboard" class="oe_searchview_dashboard">
<h4>Add to Dashboard</h4>
<form>
<input placeholder ="Title of new Dashboard item" title = "Title of new Dashboard item" type="text"/>
<button class="oe_apply" type="submit">save</button>
</form>
</div>
<t t-name="SearchView.addtodashboard.selection">
<select title = "Select Dashboard to add this filter to">
<t t-foreach="selections" t-as="element">
<option t-att-value="element.id || element.res_id "><t t-esc="element.name"/></option>
</t>
</select>
</t>
<div t-name="SearchView.advanced" class="oe_searchview_advanced">
<h4>Advanced Search</h4>
<form>
@ -1449,110 +1528,7 @@
</t>
</select>
</t>
<t t-name="AbstractFormPopup.render">
<div>
<table style="width:100%">
<tr style="width:100%">
<td style="width:100%">
<div class="oe_popup_search" style="width:100%"></div>
</td>
</tr>
<tr style="width:100%">
<td style="width:100%">
<div class="oe_popup_list" style="width:100%"></div>
</td>
</tr>
</table>
<div class="oe_popup_form" style="width:100%"></div>
</div>
</t>
<t t-name="SelectCreatePopup.search.buttons">
<button type="button" class="oe_button oe_selectcreatepopup-search-select" disabled="disabled">Select</button>
<button type="button" class="oe_button oe_selectcreatepopup-search-close">Cancel</button>
</t>
<t t-name="AbstractFormPopup.buttons">
<t t-if="! multi_select">
<button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save</button>
</t>
<t t-if="multi_select">
<button type="button" class="oe_button oe_abstractformpopup-form-save-new oe_highlight">Save &amp; New</button>
<button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save &amp; Close</button>
</t>
<button type="button" class="oe_button oe_abstractformpopup-form-close">Cancel</button>
</t>
<t t-extend="ListView.row">
<!-- adds back padding to row being rendered after edition, if necessary
(if not deletable add back padding), otherwise the row being added is
missing columns
-->
<t t-jquery="&gt; :last" t-operation="after">
<td t-if="edited and !options.deletable" class="oe_list_padding"/>
</t>
</t>
<t t-name="view_editor">
<table class="oe_view_editor">
<t t-call="view_editor.row"/>
</table>
</t>
<t t-name="view_editor.row">
<tr t-att-id="'viewedit-' + rec.id" t-att-level="rec.level" t-foreach="data" t-as="rec">
<td width="90%">
<table class="oe_view_editor_field">
<tr>
<td width="16px" t-att-style="'background-position: ' + 20*rec.level + 'px; padding-left: ' + 20*rec.level + 'px'">
<img t-if="rec.child_id.length" t-att-id="'parentimg-' + rec.id"
t-att-src='_s + "/web/static/src/img/collapse.gif"' width="16" height="16" border="0"/>
</td>
<td style="cursor: pointer;">
<a style="text-decoration:none" href="javascript:void(0);">
<t t-esc="rec.name"/>
</a>
</td>
</tr>
</table>
</td>
<td width="2%">
<img t-if="rec.att_list.length"
id="side-add" t-att-src='_s + "/web/static/src/img/icons/gtk-add.png"' style="cursor: pointer;"/>
</td>
<td width="2%">
<img id="side-remove" t-att-src='_s + "/web/static/src/img/icons/gtk-remove.png"' style="cursor: pointer;"/>
</td>
<td width="2%">
<img t-if="rec.att_list.length and !_.include(no_properties, rec.att_list[0])"
id="side-edit" t-att-src='_s + "/web/static/src/img/icons/gtk-edit.png"' style="cursor: pointer;"/>
</td>
<td width="2%">
<img t-if="rec.att_list.length"
id="side-up" t-att-src='_s + "/web/static/src/img/icons/gtk-go-up.png"' style="cursor: pointer;"/>
</td>
<td width="2%">
<img t-if="rec.att_list.length"
id="side-down" t-att-src='_s + "/web/static/src/img/icons/gtk-go-down.png"' style="cursor: pointer;"/>
</td>
<t t-if="rec.child_id.length">
<t t-set="data" t-value="rec.child_id"/>
<t t-call="view_editor.row"/>
</t>
</tr>
</t>
<t t-name="vieweditor_char">
<input type="text" t-att-id="widget.name" class="field_char" size="50"/>
</t>
<t t-name="vieweditor_selection">
<select t-att-id="widget.name" >
<t t-if="widget.selection" t-foreach="widget.selection" t-as="option">
<option
t-att-value="typeof option === 'object' ? option[0] : option">
<t t-esc="typeof option === 'object' ? option[1] : option"/>
</option>
</t>
</select>
</t>
<t t-name="vieweditor_boolean">
<input type="checkbox" t-att-id="widget.name"/>
</t>
<t t-name="ExportView">
<a id="exportview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Export</a>
</t>
@ -1733,5 +1709,12 @@
<div t-name="Many2ManyKanban.quick_create" class="oe_kanban_quick_create">
<input t-att-placeholder="_t('Type name to search')"/>
</div>
<t t-name="Throbber">
<div>
<div class="oe_blockui_spin" style="height: 50px">
</div>
<br />
<div class="oe_throbber_message" style="color:white"></div>
</div>
</t>
</templates>

View File

@ -0,0 +1,239 @@
$(document).ready(function () {
var $fix = $('#qunit-fixture');
var mod = {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
instance.web.qweb = new QWeb2.Engine();
instance.web.qweb.add_template(
'<no>' +
'<t t-name="test.widget.template">' +
'<ol>' +
'<li t-foreach="5" t-as="counter" ' +
't-attf-class="class-#{counter}">' +
'<input/>' +
'<t t-esc="counter"/>' +
'</li>' +
'</ol>' +
'</t>' +
'<t t-name="test.widget.template-value">' +
'<p><t t-esc="widget.value"/></p>' +
'</t>' +
'</no>');
}
};
var instance;
module('Widget.proxy', mod);
test('(String)', function () {
var W = instance.web.Widget.extend({
exec: function () {
this.executed = true;
}
});
var w = new W;
var fn = w.proxy('exec');
fn();
ok(w.executed, 'should execute the named method in the right context');
});
test('(String)(*args)', function () {
var W = instance.web.Widget.extend({
exec: function (arg) {
this.executed = arg;
}
});
var w = new W;
var fn = w.proxy('exec');
fn(42);
ok(w.executed, "should execute the named method in the right context");
equal(w.executed, 42, "should be passed the proxy's arguments");
});
test('(String), include', function () {
// the proxy function should handle methods being changed on the class
// and should always proxy "by name", to the most recent one
var W = instance.web.Widget.extend({
exec: function () {
this.executed = 1;
}
});
var w = new W;
var fn = w.proxy('exec');
W.include({
exec: function () { this.executed = 2; }
});
fn();
equal(w.executed, 2, "should be lazily resolved");
});
test('(Function)', function () {
var w = new (instance.web.Widget.extend({ }));
var fn = w.proxy(function () { this.executed = true; });
fn();
ok(w.executed, "should set the function's context (like Function#bind)");
});
test('(Function)(*args)', function () {
var w = new (instance.web.Widget.extend({ }));
var fn = w.proxy(function (arg) { this.executed = arg; });
fn(42);
equal(w.executed, 42, "should be passed the proxy's arguments");
});
module('Widget.renderElement', mod);
test('no template, default', function () {
var w = new (instance.web.Widget.extend({ }));
var $original = w.$el;
ok($original, "should initially have a root element");
w.renderElement();
ok(w.$el, "should have generated a root element");
ok($original !== w.$el, "should have generated a new root element");
strictEqual(w.$el, w.$el, "should provide $el alias");
ok(w.$el.is(w.el), "should provide raw DOM alias");
equal(w.el.nodeName, 'DIV', "should have generated the default element");
equal(w.el.attributes.length, 0, "should not have generated any attribute");
ok(_.isEmpty(w.$el.html(), "should not have generated any content"));
});
test('no template, custom tag', function () {
var w = new (instance.web.Widget.extend({
tagName: 'ul'
}));
w.renderElement();
equal(w.el.nodeName, 'UL', "should have generated the custom element tag");
});
test('no template, @id', function () {
var w = new (instance.web.Widget.extend({
id: 'foo'
}));
w.renderElement();
equal(w.el.attributes.length, 1, "should have one attribute");
equal(w.$el.attr('id'), 'foo', "should have generated the id attribute");
equal(w.el.id, 'foo', "should also be available via property");
});
test('no template, @className', function () {
var w = new (instance.web.Widget.extend({
className: 'oe_some_class'
}));
w.renderElement();
equal(w.el.className, 'oe_some_class', "should have the right property");
equal(w.$el.attr('class'), 'oe_some_class', "should have the right attribute");
});
test('no template, bunch of attributes', function () {
var w = new (instance.web.Widget.extend({
attributes: {
'id': 'some_id',
'class': 'some_class',
'data-foo': 'data attribute',
'clark': 'gable',
'spoiler': 'snape kills dumbledore'
}
}));
w.renderElement();
equal(w.el.attributes.length, 5, "should have all the specified attributes");
equal(w.el.id, 'some_id');
equal(w.$el.attr('id'), 'some_id');
equal(w.el.className, 'some_class');
equal(w.$el.attr('class'), 'some_class');
equal(w.$el.attr('data-foo'), 'data attribute');
equal(w.$el.data('foo'), 'data attribute');
equal(w.$el.attr('clark'), 'gable');
equal(w.$el.attr('spoiler'), 'snape kills dumbledore');
});
test('template', function () {
var w = new (instance.web.Widget.extend({
template: 'test.widget.template'
}));
w.renderElement();
equal(w.el.nodeName, 'OL');
equal(w.$el.children().length, 5);
equal(w.el.textContent, '01234');
});
module('Widget.$', mod);
test('basic-alias', function () {
var w = new (instance.web.Widget.extend({
template: 'test.widget.template'
}));
w.renderElement();
ok(w.$('li:eq(3)').is(w.$el.find('li:eq(3)')),
"should do the same thing as calling find on the widget root");
});
module('Widget.events', mod);
test('delegate', function () {
var a = [];
var w = new (instance.web.Widget.extend({
template: 'test.widget.template',
events: {
'click': function () {
a[0] = true;
strictEqual(this, w, "should trigger events in widget")
},
'click li.class-3': 'class3',
'change input': function () { a[2] = true; }
},
class3: function () { a[1] = true; }
}));
w.renderElement();
w.$el.click();
w.$('li:eq(3)').click();
w.$('input:last').val('foo').change();
for(var i=0; i<3; ++i) {
ok(a[i], "should pass test " + i);
}
});
test('undelegate', function () {
var clicked = false, newclicked = false;
var w = new (instance.web.Widget.extend({
template: 'test.widget.template',
events: { 'click li': function () { clicked = true; } }
}));
w.renderElement();
w.$el.on('click', 'li', function () { newclicked = true });
w.$('li').click();
ok(clicked, "should trigger bound events");
ok(newclicked, "should trigger bound events");
clicked = newclicked = false;
w.undelegateEvents();
w.$('li').click();
ok(!clicked, "undelegate should unbind events delegated");
ok(newclicked, "undelegate should only unbind events it created");
});
module('Widget.renderElement', mod);
test('repeated', function () {
var w = new (instance.web.Widget.extend({
template: 'test.widget.template-value'
}));
w.value = 42;
w.appendTo($fix)
.always(start)
.done(function () {
equal($fix.find('p').text(), '42', "DOM fixture should contain initial value");
equal(w.$el.text(), '42', "should set initial value");
w.value = 36;
w.renderElement();
equal($fix.find('p').text(), '36', "DOM fixture should use new value");
equal(w.$el.text(), '36', "should set new value");
});
});
});

View File

@ -12,7 +12,7 @@ $(document).ready(function () {
// Context n should have base evaluation context + all of contexts
// 0..n-1 in its own evaluation context
var active_id = 4;
var result = openerp.connection.test_eval_contexts([
var result = openerp.session.test_eval_contexts([
{
"__contexts": [
{
@ -56,7 +56,7 @@ $(document).ready(function () {
});
});
test('non-literal_eval_contexts', function () {
var result = openerp.connection.test_eval_contexts([{
var result = openerp.session.test_eval_contexts([{
"__ref": "compound_context",
"__contexts": [
{"__ref": "context", "__debug": "{'type':parent.type}",
@ -133,4 +133,20 @@ $(document).ready(function () {
}]);
deepEqual(result, {type: 'out_invoice'});
});
module('eval.domains', {
setup: function () {
openerp = window.openerp.testing.instanceFor('coresetup');
window.openerp.web.dates(openerp);
}
});
test('current_date', function () {
var current_date = openerp.web.date_to_str(new Date());
var result = openerp.session.test_eval_domains(
[[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}],
openerp.session.test_eval_get_context());
deepEqual(result, [
['name', '>=', current_date],
['name', '<=', current_date]
]);
})
});

View File

@ -7,7 +7,7 @@ $(document).ready(function () {
t.module('Dataset shortcuts', 'data');
t.test('read_index', function (openerp) {
var ds = new openerp.web.DataSet(
{session: openerp.connection}, 'some.model');
{session: openerp.session}, 'some.model');
ds.ids = [10, 20, 30, 40, 50];
ds.index = 2;
t.expect(ds.read_index(['a', 'b', 'c']), function (result) {
@ -24,7 +24,7 @@ $(document).ready(function () {
});
t.test('default_get', function (openerp) {
var ds = new openerp.web.DataSet(
{session: openerp.connection}, 'some.model', {foo: 'bar'});
{session: openerp.session}, 'some.model', {foo: 'bar'});
t.expect(ds.default_get(['a', 'b', 'c']), function (result) {
strictEqual(result.method, 'default_get');
strictEqual(result.model, 'some.model');
@ -38,7 +38,7 @@ $(document).ready(function () {
});
});
t.test('create', function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.connection}, 'some.model');
var ds = new openerp.web.DataSet({session: openerp.session}, 'some.model');
t.expect(ds.create({foo: 1, bar: 2}), function (r) {
strictEqual(r.method, 'create');
@ -51,7 +51,7 @@ $(document).ready(function () {
});
});
t.test('write', function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod');
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');
t.expect(ds.write(42, {foo: 1}), function (r) {
strictEqual(r.method, 'write');
@ -69,7 +69,7 @@ $(document).ready(function () {
// });
});
t.test('unlink', function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod');
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');
t.expect(ds.unlink([42]), function (r) {
strictEqual(r.method, 'unlink');
@ -81,7 +81,7 @@ $(document).ready(function () {
});
});
t.test('call', function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod');
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');
t.expect(ds.call('frob', ['a', 'b', 42]), function (r) {
strictEqual(r.method, 'frob');
@ -92,7 +92,7 @@ $(document).ready(function () {
});
});
t.test('name_get', function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod');
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');
t.expect(ds.name_get([1, 2], null), function (r) {
strictEqual(r.method, 'name_get');
@ -104,7 +104,7 @@ $(document).ready(function () {
});
});
t.test('name_search, name', function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod');
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');
t.expect(ds.name_search('bob'), function (r) {
strictEqual(r.method, 'name_search');
@ -119,7 +119,7 @@ $(document).ready(function () {
});
});
t.test('name_search, domain & operator', function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod');
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');
t.expect(ds.name_search(0, [['foo', '=', 3]], 'someop'), function (r) {
strictEqual(r.method, 'name_search');
@ -134,7 +134,7 @@ $(document).ready(function () {
});
});
t.test('exec_workflow', function (openerp) {
var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod');
var ds = new openerp.web.DataSet({session: openerp.session}, 'mod');
t.expect(ds.exec_workflow(42, 'foo'), function (r) {
strictEqual(r['service'], 'object');
strictEqual(r.method, 'exec_workflow');
@ -147,7 +147,7 @@ $(document).ready(function () {
});
t.test('DataSetSearch#read_slice', function (openerp) {
var ds = new openerp.web.DataSetSearch({session: openerp.connection}, 'mod');
var ds = new openerp.web.DataSetSearch({session: openerp.session}, 'mod');
t.expect(ds.read_slice(['foo', 'bar'], {
domain: [['foo', '>', 42], ['qux', '=', 'grault']],
context: {peewee: 'herman'},
@ -167,7 +167,7 @@ $(document).ready(function () {
});
});
t.test('DataSetSearch#read_slice sorted', function (openerp) {
var ds = new openerp.web.DataSetSearch({session: openerp.connection}, 'mod');
var ds = new openerp.web.DataSetSearch({session: openerp.session}, 'mod');
ds.sort('foo');
ds.sort('foo');
ds.sort('bar');
@ -194,7 +194,7 @@ $(document).ready(function () {
});
t.test('Dataset', function (openerp) {
var ds = new openerp.web.DataSetSearch(
{session: openerp.connection}, 'mod');
{session: openerp.session}, 'mod');
var c = new openerp.web.CompoundContext(
{a: 'foo', b: 3, c: 5}, openerp.contexts[0]);
t.expect(ds.read_slice(['foo', 'bar'], {
@ -220,7 +220,7 @@ $(document).ready(function () {
parent: {model: 'qux'}
};
var ds = new openerp.web.DataSet(
{session: openerp.connection}, 'mod',
{session: openerp.session}, 'mod',
new openerp.web.CompoundContext({})
.set_eval_context(eval_context));
var domain = new openerp.web.CompoundDomain(openerp.domains[0])

View File

@ -0,0 +1,349 @@
$(document).ready(function () {
var $fix = $('#qunit-fixture');
var instance;
var baseSetup = function () {
instance = openerp.testing.instanceFor('list_editable');
openerp.testing.loadTemplate(instance);
openerp.testing.mockifyRPC(instance);
};
/**
*
* @param {String} name
* @param {Object} [attrs]
* @param {String} [attrs.type="char"]
* @param {Boolean} [attrs.required]
* @param {Boolean} [attrs.invisible]
* @param {Boolean} [attrs.readonly]
* @return {Object}
*/
function field(name, attrs) {
attrs = attrs || {};
attrs.name = name;
return _.defaults(attrs, {
type: 'char'
});
}
/**
* @param {Array} fields
* @return {Object}
*/
function makeFormView(fields) {
var fobj = {};
_(fields).each(function (field) {
fobj[field.name] = {
type: field.type,
string: field.string
};
});
var children = _(fields).map(function (field) {
return {
tag: 'field',
attrs: {
name: field.name,
modifiers: JSON.stringify({
required: field.required,
invisible: field.invisible,
readonly: field.readonly
})
}
}
});
return {
arch: {
tag: 'form',
attrs: {
version: '7.0',
'class': 'oe_form_container'
},
children: children
},
fields: fobj
};
}
module('editor', {
setup: baseSetup
});
asyncTest('base-state', 2, function () {
var e = new instance.web.list.Editor({
dataset: {ids: []},
edition_view: function () {
return makeFormView();
}
});
e.appendTo($fix)
.always(start)
.fail(function (error) { ok(false, error && error.message); })
.done(function () {
ok(!e.is_editing(), "should not be editing");
ok(e.form instanceof instance.web.FormView,
"should use default form type");
});
});
asyncTest('toggle-edition-save', 4, function () {
instance.session.responses['/web/dataset/call_kw:create'] = function () {
return { result: 42 };
};
instance.session.responses['/web/dataset/call_kw:read'] = function () {
return { result: [{
id: 42,
a: false,
b: false,
c: false
}]};
};
var e = new instance.web.list.Editor({
dataset: new instance.web.DataSetSearch(),
prepends_on_create: function () { return false; },
edition_view: function () {
return makeFormView([ field('a'), field('b'), field('c') ]);
}
});
var counter = 0;
e.appendTo($fix)
.pipe(function () {
return e.edit({}, function () {
++counter;
});
})
.pipe(function (form) {
ok(e.is_editing(), "should be editing");
equal(counter, 3, "should have configured all fields");
return e.save();
})
.always(start)
.fail(function (error) { ok(false, error && error.message); })
.done(function (record) {
ok(!e.is_editing(), "should have stopped editing");
equal(record.id, 42, "should have newly created id");
})
});
asyncTest('toggle-edition-cancel', 2, function () {
instance.session.responses['/web/dataset/call_kw:create'] = function () {
return { result: 42 };
};
var e = new instance.web.list.Editor({
dataset: new instance.web.DataSetSearch(),
prepends_on_create: function () { return false; },
edition_view: function () {
return makeFormView([ field('a'), field('b'), field('c') ]);
}
});
var counter = 0;
e.appendTo($fix)
.pipe(function () {
return e.edit({}, function () {
++counter;
});
})
.pipe(function (form) {
return e.cancel();
})
.always(start)
.fail(function (error) { ok(false, error && error.message); })
.done(function (record) {
ok(!e.is_editing(), "should have stopped editing");
ok(!record.id, "should have no id");
})
});
asyncTest('toggle-save-required', 2, function () {
instance.session.responses['/web/dataset/call_kw:create'] = function () {
return { result: 42 };
};
var e = new instance.web.list.Editor({
do_warn: function () {
warnings++;
},
dataset: new instance.web.DataSetSearch(),
prepends_on_create: function () { return false; },
edition_view: function () {
return makeFormView([
field('a', {required: true}), field('b'), field('c') ]);
}
});
var counter = 0;
var warnings = 0;
e.appendTo($fix)
.pipe(function () {
return e.edit({}, function () {
++counter;
});
})
.pipe(function (form) {
return e.save();
})
.always(start)
.done(function () { ok(false, "cancel should not succeed"); })
.fail(function () {
equal(warnings, 1, "should have been warned");
ok(e.is_editing(), "should have kept editing");
})
});
module('list-edition', {
setup: function () {
baseSetup();
var records = {};
_.extend(instance.session.responses, {
'/web/listview/load': function () {
return {result: {
type: 'tree',
fields: {
a: {type: 'char', string: "A"},
b: {type: 'char', string: "B"},
c: {type: 'char', string: "C"}
},
arch: {
tag: 'tree',
attrs: {},
children: [
{tag: 'field', attrs: {name: 'a'}},
{tag: 'field', attrs: {name: 'b'}},
{tag: 'field', attrs: {name: 'c'}}
]
}
}};
},
'/web/dataset/call_kw:create': function (params) {
records[42] = _.extend({}, params.params.args[0]);
return {result: 42};
},
'/web/dataset/call_kw:read': function (params) {
var id = params.params.args[0][0];
if (id in records) {
return {result: [records[id]]};
}
return {result: []};
}
})
}
});
asyncTest('newrecord', 6, function () {
var got_defaults = false;
instance.session.responses['/web/dataset/call_kw:default_get'] = function (params) {
var fields = params.params.args[0];
deepEqual(
fields, ['a', 'b', 'c'],
"should ask defaults for all fields");
got_defaults = true;
return {result: {
a: "qux",
b: "quux"
}};
};
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
l.appendTo($fix)
.pipe(l.proxy('reload_content'))
.pipe(function () {
return l.start_edition();
})
.always(start)
.pipe(function () {
ok(got_defaults, "should have fetched default values for form");
return l.save_edition();
})
.pipe(function (result) {
ok(result.created, "should yield newly created record");
equal(result.record.get('a'), "qux",
"should have used default values");
equal(result.record.get('b'), "quux",
"should have used default values");
ok(!result.record.get('c'),
"should have no value if there was no default");
})
.fail(function (e) { ok(false, e && e.message || e); });
});
module('list-edition-events', {
setup: function () {
baseSetup();
_.extend(instance.session.responses, {
'/web/listview/load': function () {
return {result: {
type: 'tree',
fields: {
a: {type: 'char', string: "A"},
b: {type: 'char', string: "B"},
c: {type: 'char', string: "C"}
},
arch: {
tag: 'tree',
attrs: {},
children: [
{tag: 'field', attrs: {name: 'a'}},
{tag: 'field', attrs: {name: 'b'}},
{tag: 'field', attrs: {name: 'c'}}
]
}
}};
},
'/web/dataset/call_kw:read': function (params) {
return {result: [{
id: 1,
a: 'foo',
b: 'bar',
c: 'baz'
}]};
}
});
}
});
asyncTest('edition events', 4, function () {
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
var o = {
counter: 0,
onEvent: function (e) { this.counter++; }
};
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
l.on('edit:before edit:after', o, o.onEvent);
l.appendTo($fix)
.pipe(l.proxy('reload_content'))
.always(start)
.pipe(function () {
ok(l.options.editable, "should be editable");
equal(o.counter, 0, "should have seen no event yet");
return l.start_edition(l.records.get(1));
})
.pipe(function () {
ok(l.editor.is_editing(), "should be editing");
equal(o.counter, 2, "should have seen two edition events");
})
.fail(function (e) { ok(false, e && e.message); });
});
asyncTest('edition events: cancelling', 3, function () {
var edit_after = false;
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
l.on('edit:before', {}, function (e) {
e.cancel = true;
});
l.on('edit:after', {}, function () {
edit_after = true;
});
l.appendTo($fix)
.pipe(l.proxy('reload_content'))
.always(start)
.pipe(function () {
ok(l.options.editable, "should be editable");
return l.start_edition();
})
// cancelling an event rejects the deferred
.pipe($.Deferred().reject(), function () {
ok(!l.editor.is_editing(), "should not be editing");
ok(!edit_after, "should not have fired the edit:after event");
return $.when();
})
.fail(function (e) { ok(false, e && e.message || e); });
});
});

View File

@ -133,7 +133,7 @@ $(document).ready(function () {
strictEqual(changed, 1);
});
module('list-collections-degenerate', {
module('list-collections', {
setup: function () {
openerp = window.openerp.init([]);
window.openerp.web.corelib(openerp);
@ -145,7 +145,7 @@ $(document).ready(function () {
window.openerp.web.list(openerp);
}
});
test('Fetch from collection', function () {
test('degenerate-fetch', function () {
var c = new openerp.web.list.Collection();
strictEqual(c.length, 0);
c.add({id: 1, value: 2});
@ -163,7 +163,7 @@ $(document).ready(function () {
strictEqual(r2.get('id'), 1);
strictEqual(r2.get('value'), 2);
});
test('Add at index', function () {
test('degenerate-indexed-add', function () {
var c = new openerp.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
@ -175,7 +175,7 @@ $(document).ready(function () {
strictEqual(c.at(1).get('value'), 55);
strictEqual(c.at(3).get('value'), 20);
});
test('Remove record', function () {
test('degenerate-remove', function () {
var c = new openerp.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
@ -188,7 +188,7 @@ $(document).ready(function () {
equal(c.get(2), undefined);
strictEqual(c.at(1).get('value'), 20);
});
test('Remove unbind', function () {
test('degenerate-remove-bound', function () {
var changed = false,
c = new openerp.web.list.Collection([ {id: 1, value: 5} ]);
c.bind('change', function () { changed = true; });
@ -198,7 +198,7 @@ $(document).ready(function () {
ok(!changed, 'removed records should not trigger events in their ' +
'parent collection');
});
test('Reset', function () {
test('degenerate-reset', function () {
var event, obj, c = new openerp.web.list.Collection([
{id: 1, value: 5},
{id: 2, value: 10},
@ -218,7 +218,7 @@ $(document).ready(function () {
strictEqual(c.length, 1);
strictEqual(c.get(42).get('value'), 55);
});
test('Reset unbind', function () {
test('degenerate-reset-bound', function () {
var changed = false,
c = new openerp.web.list.Collection([ {id: 1, value: 5} ]);
c.bind('change', function () { changed = true; });
@ -229,7 +229,7 @@ $(document).ready(function () {
'parent collection');
});
test('Events propagation', function () {
test('degenerate-propagations', function () {
var values = [];
var c = new openerp.web.list.Collection([
{id: 1, value: 5},
@ -260,6 +260,82 @@ $(document).ready(function () {
c.at(1).set('wealth', 5);
strictEqual(total, 47);
});
test('degenerate-successor', function () {
var root = new openerp.web.list.Collection([
{id: 1, value: 1},
{id: 2, value: 2},
{id: 3, value: 3},
{id: 4, value: 5},
{id: 5, value: 8}
]);
deepEqual(root.succ(root.at(2)).attributes,
root.at(3).attributes,
"should return the record at (index + 1) from the pivot");
equal(root.succ(root.at(4)), null,
"should return null as successor to last record");
deepEqual(root.succ(root.at(4), {wraparound: true}).attributes,
root.at(0).attributes,
"should return index 0 as successor to last record if" +
" wraparound is set");
deepEqual(root.succ(root.at(2), {wraparound: true}).attributes,
root.at(3).attributes,
"wraparound should have no effect if not succ(last_record)");
});
test('successor', function () {
var root = new openerp.web.list.Collection();
root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]);
root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]);
root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]);
deepEqual(root.succ(root.get(3)).attributes,
root.get(4).attributes,
"should get successor");
equal(root.succ(root.get(4)),
null,
"successors do not cross collections");
deepEqual(root.succ(root.get(4), {wraparound: true}).attributes,
root.get(3).attributes,
"should wraparound within a collection");
});
test('degenerate-predecessor', function () {
var root = new openerp.web.list.Collection([
{id: 1, value: 1},
{id: 2, value: 2},
{id: 3, value: 3},
{id: 4, value: 5},
{id: 5, value: 8}
]);
deepEqual(root.pred(root.at(2)).attributes,
root.at(1).attributes,
"should return the record at (index - 1) from the pivot");
equal(root.pred(root.at(0)), null,
"should return null as predecessor to first record");
deepEqual(root.pred(root.at(0), {wraparound: true}).attributes,
root.at(4).attributes,
"should return last record as predecessor to first record" +
" if wraparound is set");
deepEqual(root.pred(root.at(1), {wraparound: true}).attributes,
root.at(0).attributes,
"wraparound should have no effect if not pred(first_record)");
});
test('predecessor', function () {
var root = new openerp.web.list.Collection();
root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]);
root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]);
root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]);
deepEqual(root.pred(root.get(4)).attributes,
root.get(3).attributes,
"should get predecessor");
equal(root.pred(root.get(3)),
null,
"predecessor do not cross collections");
deepEqual(root.pred(root.get(3), {wraparound: true}).attributes,
root.get(4).attributes,
"should wraparound within a collection");
});
module('list-hofs', {
setup: function () {
@ -338,4 +414,33 @@ $(document).ready(function () {
ids, [1, 2, 3, 10, 20, 30],
'tree collections should be deeply iterated');
});
module("list-weirds", {
setup: function () {
openerp = window.openerp.init([]);
window.openerp.web.corelib(openerp);
window.openerp.web.coresetup(openerp);
window.openerp.web.chrome(openerp);
// views loader stuff
window.openerp.web.data(openerp);
window.openerp.web.views(openerp);
window.openerp.web.list(openerp);
}
});
test('set-from-noid', function () {
var root = new openerp.web.list.Collection();
root.add({v: 3});
root.at(0).set('id', 42);
var record = root.get(42);
equal(root.length, 1);
equal(record.get('v'), 3, "should have fetched the original record");
});
test('set-from-previd', function () {
var root = new openerp.web.list.Collection();
root.add({id: 1, v: 2});
root.get(1).set('id', 42);
var record = root.get(42);
equal(root.length, 1);
equal(record.get('v'), 2, "should have fetched the original record");
});
});

View File

@ -9,21 +9,6 @@ $(document).ready(function () {
openerp.web.Foo2 = {};
}
});
test('key fetch', function () {
var reg = new openerp.web.Registry({
foo: 'openerp.web.Foo',
bar: 'openerp.web.Bar',
quux: 'openerp.web.Quux'
});
strictEqual(reg.get_object('foo'), openerp.web.Foo);
raises(function () { reg.get_object('qux'); },
openerp.web.KeyNotFound,
"Unknown keys should raise KeyNotFound");
raises(function () { reg.get_object('quux'); },
openerp.web.ObjectNotFound,
"Incorrect file paths should raise ObjectNotFound");
});
test('key set', function () {
var reg = new openerp.web.Registry();

View File

@ -1,37 +1,8 @@
$(document).ready(function () {
var xhr = QWeb2.Engine.prototype.get_xhr();
xhr.open('GET', '/web/static/src/xml/base.xml', false);
xhr.send(null);
var doc = xhr.responseXML;
var noop = function () {};
/**
* Make connection RPC responses mockable by setting keys on the
* Connection#responses object (key is the URL, value is the function to
* call with the RPC request payload)
*
* @param {openerp.web.Connection} connection connection instance to mockify
* @param {Object} [responses] url:function mapping to seed the mock connection
*/
var mockifyRPC = function (connection, responses) {
connection.responses = responses || {};
connection.rpc_function = function (url, payload) {
if (!(url.url in this.responses)) {
return $.Deferred().reject({}, 'failed', _.str.sprintf("Url %s not found in mock responses", url.url)).promise();
}
return $.when(this.responses[url.url](payload));
};
};
var instance;
module('query', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
window.openerp.web.search(instance);
instance = openerp.testing.instanceFor('search');
}
});
test('Adding a facet to the query creates a facet and a value', function () {
@ -167,16 +138,11 @@ $(document).ready(function () {
module('defaults', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
window.openerp.web.search(instance);
instance = openerp.testing.instanceFor('search');
instance.web.qweb.add_template(doc);
openerp.testing.loadTemplate(instance);
mockifyRPC(instance.connection);
openerp.testing.mockifyRPC(instance);
}
});
@ -198,8 +164,8 @@ $(document).ready(function () {
instance.dummy = {};
instance.dummy.DummyWidget = instance.web.search.Field.extend(
dummy_widget_attributes || {});
if (!('/web/searchview/load' in instance.connection.responses)) {
instance.connection.responses['/web/searchview/load'] = function () {
if (!('/web/searchview/load' in instance.session.responses)) {
instance.session.responses['/web/searchview/load'] = function () {
return {result: {fields_view: {
type: 'search',
fields: {
@ -220,10 +186,10 @@ $(document).ready(function () {
}}};
};
}
instance.connection.responses['/web/searchview/get_filters'] = function () {
instance.session.responses['/web/searchview/get_filters'] = function () {
return {result: []};
};
instance.connection.responses['/web/searchview/fields_get'] = function () {
instance.session.responses['/web/searchview/fields_get'] = function () {
return {result: {fields: {
dummy: {type: 'char', string: 'Dummy'}
}}};
@ -355,7 +321,7 @@ $(document).ready(function () {
{attrs: {name: 'dummy', string: 'Dummy'}},
{relation: 'dummy.model.name'},
view);
instance.connection.responses['/web/dataset/call_kw'] = function (req) {
instance.session.responses['/web/dataset/call_kw'] = function (req) {
equal(req.params.method, 'name_get',
"m2o should resolve default id");
equal(req.params.model, f.attrs.relation,
@ -391,7 +357,7 @@ $(document).ready(function () {
{attrs: {name: 'dummy', string: 'Dummy'}},
{relation: 'dummy.model.name'},
view);
instance.connection.responses['/web/dataset/call_kw'] = function (req) {
instance.session.responses['/web/dataset/call_kw'] = function (req) {
return {result: []};
};
f.facet_for_defaults({dummy: id})
@ -404,18 +370,11 @@ $(document).ready(function () {
module('completions', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
// date complete
window.openerp.web.formats(instance);
window.openerp.web.search(instance);
instance = openerp.testing.instanceFor('search');
instance.web.qweb.add_template(doc);
openerp.testing.loadTemplate(instance);
mockifyRPC(instance.connection);
openerp.testing.mockifyRPC(instance);
}
});
asyncTest('calling', 4, function () {
@ -432,10 +391,7 @@ $(document).ready(function () {
}
});
view.appendTo($('#qunit-fixture'))
.always(start)
.fail(function (error) { ok(false, error.message); })
.done(function () {
stop();
view.complete_global_search({term: "dum"}, function (completions) {
start();
equal(completions.length, 1, "should have a single completion");
@ -454,7 +410,11 @@ $(document).ready(function () {
var completion = {
label: "Dummy",
facet: {
field: {get_domain: noop, get_context: noop, get_groupby: noop},
field: {
get_domain: openerp.testing.noop,
get_context: openerp.testing.noop,
get_groupby: openerp.testing.noop
},
category: 'Dummy',
values: [{label: 'dummy', value: 42}]
}
@ -476,7 +436,11 @@ $(document).ready(function () {
});
});
asyncTest('facet selection: new value existing facet', 3, function () {
var field = {get_domain: noop, get_context: noop, get_groupby: noop};
var field = {
get_domain: openerp.testing.noop,
get_context: openerp.testing.noop,
get_groupby: openerp.testing.noop
};
var completion = {
label: "Dummy",
facet: {
@ -609,7 +573,7 @@ $(document).ready(function () {
});
});
asyncTest("M2O", 15, function () {
instance.connection.responses['/web/dataset/call_kw'] = function (req) {
instance.session.responses['/web/dataset/call_kw'] = function (req) {
equal(req.params.method, "name_search");
equal(req.params.model, "dummy.model");
deepEqual(req.params.args, []);
@ -643,7 +607,7 @@ $(document).ready(function () {
});
});
asyncTest("M2O no match", 5, function () {
instance.connection.responses['/web/dataset/call_kw'] = function (req) {
instance.session.responses['/web/dataset/call_kw'] = function (req) {
equal(req.params.method, "name_search");
equal(req.params.model, "dummy.model");
deepEqual(req.params.args, []);
@ -663,16 +627,11 @@ $(document).ready(function () {
module('search-serialization', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
window.openerp.web.search(instance);
instance = openerp.testing.instanceFor('search');
instance.web.qweb.add_template(doc);
openerp.testing.loadTemplate(instance);
mockifyRPC(instance.connection);
openerp.testing.mockifyRPC(instance);
}
});
asyncTest('No facet, no call', 6, function () {
@ -940,16 +899,11 @@ $(document).ready(function () {
module('removal', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
window.openerp.web.search(instance);
instance = openerp.testing.instanceFor('search');
instance.web.qweb.add_template(doc);
openerp.testing.loadTemplate(instance);
mockifyRPC(instance.connection);
openerp.testing.mockifyRPC(instance);
}
});
asyncTest('clear button', function () {
@ -975,16 +929,11 @@ $(document).ready(function () {
module('drawer', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
window.openerp.web.search(instance);
instance = openerp.testing.instanceFor('search');
instance.web.qweb.add_template(doc);
openerp.testing.loadTemplate(instance);
mockifyRPC(instance.connection);
openerp.testing.mockifyRPC(instance);
}
});
asyncTest('is-drawn', 2, function () {
@ -1003,16 +952,11 @@ $(document).ready(function () {
module('filters', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
window.openerp.web.search(instance);
instance = openerp.testing.instanceFor('search');
instance.web.qweb.add_template(doc);
openerp.testing.loadTemplate(instance);
mockifyRPC(instance.connection, {
openerp.testing.mockifyRPC(instance, {
'/web/searchview/load': function () {
// view with a single group of filters
return {result: {fields_view: {
@ -1117,22 +1061,16 @@ $(document).ready(function () {
module('saved_filters', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
window.openerp.web.formats(instance);
window.openerp.web.search(instance);
instance = openerp.testing.instanceFor('search');
instance.web.qweb.add_template(doc);
openerp.testing.loadTemplate(instance);
mockifyRPC(instance.connection);
openerp.testing.mockifyRPC(instance);
}
});
asyncTest('checkboxing', 6, function () {
var view = makeSearchView();
instance.connection.responses['/web/searchview/get_filters'] = function () {
instance.session.responses['/web/searchview/get_filters'] = function () {
return {result: [{
name: "filter name",
user_id: 42
@ -1161,7 +1099,7 @@ $(document).ready(function () {
});
asyncTest('removal', 1, function () {
var view = makeSearchView();
instance.connection.responses['/web/searchview/get_filters'] = function () {
instance.session.responses['/web/searchview/get_filters'] = function () {
return {result: [{
name: "filter name",
user_id: 42
@ -1183,17 +1121,11 @@ $(document).ready(function () {
module('advanced', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
window.openerp.web.formats(instance);
window.openerp.web.search(instance);
instance = openerp.testing.instanceFor('search');
instance.web.qweb.add_template(doc);
openerp.testing.loadTemplate(instance);
mockifyRPC(instance.connection);
openerp.testing.mockifyRPC(instance);
}
});
asyncTest('single-advanced', 6, function () {

View File

@ -13,7 +13,7 @@
<script src="/web/static/lib/backbone/backbone.js" type="text/javascript"></script>
<!-- jquery -->
<script src="/web/static/lib/jquery/jquery-1.7.2b1.js"></script>
<script src="/web/static/lib/jquery/jquery-1.7.2.js"></script>
<script src="/web/static/lib/jquery.ui/js/jquery-ui-1.8.17.custom.min.js"></script>
<script src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
@ -38,6 +38,12 @@
<script src="/web/static/src/js/search.js"></script>
<script src="/web/static/src/js/view_form.js"></script>
<script src="/web/static/src/js/view_list.js"></script>
<script src="/web/static/src/js/view_list_editable.js"></script>
<script src="/web/static/test/testing.js"></script>
<script type="text/javascript">
QUnit.config.testTimeout = 2000;
</script>
</head>
<body id="oe" class="openerp">
<h1 id="qunit-header">OpenERP web Test Suite</h1>
@ -55,4 +61,6 @@
<script type="text/javascript" src="/web/static/test/rpc.js"></script>
<script type="text/javascript" src="/web/static/test/evals.js"></script>
<script type="text/javascript" src="/web/static/test/search.js"></script>
<script type="text/javascript" src="/web/static/test/Widget.js"></script>
<script type="text/javascript" src="/web/static/test/list-editable.js"></script>
</html>

View File

@ -0,0 +1,97 @@
// Test support structures and methods for OpenERP
openerp.testing = (function () {
var xhr = QWeb2.Engine.prototype.get_xhr();
xhr.open('GET', '/web/static/src/xml/base.xml', false);
xhr.send(null);
var doc = xhr.responseXML;
var dependencies = {
corelib: [],
coresetup: ['corelib'],
data: ['corelib', 'coresetup'],
dates: [],
formats: ['coresetup', 'dates'],
chrome: ['corelib', 'coresetup'],
views: ['corelib', 'coresetup', 'data', 'chrome'],
search: ['data', 'coresetup', 'formats'],
list: ['views', 'data'],
form: ['data', 'views', 'list', 'formats'],
list_editable: ['list', 'form', 'data'],
};
return {
/**
* Function which does not do anything
*/
noop: function () { },
/**
* Loads 'base.xml' template file into qweb for the provided instance
*
* @param instance openerp instance being initialized, to load the template file in
*/
loadTemplate: function (instance) {
instance.web.qweb.add_template(doc);
},
/**
* Alter provided instance's ``session`` attribute to make response
* mockable:
*
* * The ``responses`` parameter can be used to provide a map of (RPC)
* paths (e.g. ``/web/view/load``) to a function returning a response
* to the query.
* * ``instance.session`` grows a ``responses`` attribute which is
* a map of the same (and is in fact initialized to the ``responses``
* parameter if one is provided)
*
* Note that RPC requests to un-mocked URLs will be rejected with an
* error message: only explicitly specified urls will get a response.
*
* Mocked sessions will *never* perform an actual RPC connection.
*
* @param instance openerp instance being initialized
* @param {Object} [responses]
*/
mockifyRPC: function (instance, responses) {
var session = instance.session;
session.responses = responses || {};
session.rpc_function = function (url, payload) {
var fn = this.responses[url.url + ':' + payload.params.method]
|| this.responses[url.url];
if (!fn) {
return $.Deferred().reject({}, 'failed',
_.str.sprintf("Url %s not found in mock responses, with arguments %s",
url.url, JSON.stringify(payload.params))
).promise();
}
return $.when(fn(payload));
};
},
/**
* Creates an openerp web instance loading the specified module after
* all of its dependencies.
*
* @param {String} module
* @returns OpenERP Web instance
*/
instanceFor: function (module) {
var instance = openerp.init([]);
this._load(instance, module);
return instance;
},
_load: function (instance, module, loaded) {
if (!loaded) { loaded = []; }
var deps = dependencies[module];
if (!deps) { throw new Error("Unknown dependencies for " + module); }
var to_load = _.difference(deps, loaded);
while (!_.isEmpty(to_load)) {
this._load(instance, to_load[0], loaded);
to_load = _.difference(deps, loaded);
}
openerp.web[module](instance);
loaded.push(module);
}
}
})();

View File

@ -39,5 +39,4 @@ class TestSession(Session):
"context": CONTEXT,
"db": req.session._db,
"login": req.session._login,
"openerp_entreprise": False,
}

View File

@ -0,0 +1,17 @@
{
'name': 'OpenERP Web API',
'category': 'Hidden',
'description': """
Openerp Web API.
================
""",
'version': '2.0',
'depends': ['web'],
'installable': True,
'auto_install': False,
'js' : [
],
'css' : [
],
}

View File

@ -0,0 +1,20 @@
# Spanish (Costa Rica) translation for openerp-web
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openerp-web package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openerp-web\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-07-02 09:06+0200\n"
"PO-Revision-Date: 2012-02-16 19:19+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Spanish (Costa Rica) <es_CR@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-08-15 04:50+0000\n"
"X-Generator: Launchpad (build 15801)\n"

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html style="height: 100%">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<style type="text/css">
body {
font-family: sans-serif;
}
pre.run {
border: 1px solid black; margin:0;padding:8px;
}
.result {
border: 1px solid black; margin:0;padding:8px;
}
</style>
<title>OpenERP web_api example</title>
<script type="text/javascript" src="/web/webclient/js"></script>
<script type="text/javascript">
$(function() {
$("body").on('click','button',function(ev){
eval($("pre.run").text());
});
});
</script>
</head>
<body>
<h1>OpenERP web_api examples</h1>
<h2>Example 1: Load the list of defined ir.model <button>Run it !</button></h2>
<blockquote>
<h3>Code: </h3>
<pre>
&lt;script type="text/javascript" src="/web/webclient/js"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
<pre id="ex1" class="run">
var instance = openerp.init(["web"]); // get a new instance
instance.session.session_bind(); // bind it to the right hostname
instance.session.session_authenticate("trunk", "admin", "admin", false).then(function() {
var ds = new instance.web.DataSetSearch(null, "ir.model");
ds.read_slice(['name','model'], {}).then(function(models){
_.each(models,function(m,i){
$("#ex1res").append("&lt;li&gt;" + m.model + " (" + m.name + ") &lt;/li&gt;")
});
});
});
</pre>&lt;/script&gt;
</pre>
<h3>Div for output:</h3>
<div id="ex1res" class="result">
&nbsp;
</div>
</blockquote>
<h2>Help me to complete this examples on <a href="http://bazaar.launchpad.net/~openerp/openerp-web/trunk/view/head:/addons/web_api/static/src/example.html">launchpad</a></h2>
</body>
</html>

View File

@ -1,23 +1,24 @@
{
"name": "Web Calendar",
"category": "Hidden",
"description":
"""
OpenERP Web Calendar view.
""",
"version": "2.0",
"depends": ['web'],
"js": [
'name': 'Web Calendar',
'category': 'Hidden',
'description':"""
OpenERP Web Calendar view.
==========================
""",
'version': '2.0',
'depends': ['web'],
'js': [
'static/lib/dhtmlxScheduler/codebase/dhtmlxscheduler_debug.js',
'static/lib/dhtmlxScheduler/sources/ext/ext_minical.js',
'static/src/js/calendar.js'
],
"css": [
'static/lib/dhtmlxScheduler/codebase/ext/dhtmlxscheduler_ext.css',
'static/src/css/web_calendar.css'
],
'css': [
'static/lib/dhtmlxScheduler/codebase/ext/dhtmlxscheduler_ext.css',
'static/src/css/web_calendar.css'
],
'qweb' : [
"static/src/xml/*.xml",
'static/src/xml/*.xml',
],
'auto_install': True
}

View File

@ -14,129 +14,129 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-07-03 05:55+0000\n"
"X-Generator: Launchpad (build 15531)\n"
"X-Launchpad-Export-Date: 2012-08-28 07:23+0000\n"
"X-Generator: Launchpad (build 15864)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:12
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr "التقويم"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:73
#: addons/web_calendar/static/src/js/calendar.js:70
msgid "Filter"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:139
msgid "Today"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:140
msgid "Day"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:141
msgid "Week"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:142
msgid "Month"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:143
msgid "New event"
msgstr ""
msgstr "مرشّح"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:144
msgid "Save"
msgstr ""
msgid "Today"
msgstr "اليوم"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:145
msgid "Cancel"
msgstr ""
msgid "Day"
msgstr "اليوم"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:146
msgid "Details"
msgstr ""
msgid "Week"
msgstr "الأسبوع"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:147
msgid "Edit"
msgstr ""
msgid "Month"
msgstr "الشهر"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:148
msgid "Delete"
msgstr ""
msgid "New event"
msgstr "حدث جديد"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:149
msgid "Save"
msgstr "حفظ"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:150
msgid "Event will be deleted permanently, are you sure?"
msgstr ""
msgid "Cancel"
msgstr "إلغاء"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:151
#: addons/web_calendar/static/src/js/calendar.js:164
msgid "Description"
msgstr ""
msgid "Details"
msgstr "التفاصيل"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:152
msgid "Time period"
msgstr ""
msgid "Edit"
msgstr "تحرير"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:153
msgid "Full day"
msgstr ""
msgid "Delete"
msgstr "حذف"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:155
msgid "Event will be deleted permanently, are you sure?"
msgstr "سيتم حذف هذا الحدث بشكل دائم، هل أنت متأكد؟"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:156
msgid "Do you want to edit the whole set of repeated events?"
msgstr ""
#: addons/web_calendar/static/src/js/calendar.js:169
msgid "Description"
msgstr "الوصف"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:157
msgid "Repeat event"
msgstr ""
msgid "Time period"
msgstr "الفترة الزمنية"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:158
msgid "Disabled"
msgstr ""
msgid "Full day"
msgstr "يوم كامل"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:159
msgid "Enabled"
msgstr ""
#: addons/web_calendar/static/src/js/calendar.js:161
msgid "Do you want to edit the whole set of repeated events?"
msgstr "هل تريد تحرير مجموعة الأحداث المتكررة كاملة؟"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:162
#: addons/web_calendar/static/src/js/calendar.js:170
msgid "Agenda"
msgstr ""
msgid "Repeat event"
msgstr "تكرار الحدث"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:163
msgid "Date"
msgstr ""
msgid "Disabled"
msgstr "معطّل"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:164
msgid "Enabled"
msgstr "مُفعّل"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:167
msgid "Year"
msgstr ""
#: addons/web_calendar/static/src/js/calendar.js:175
msgid "Agenda"
msgstr "جدول أعمال"
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:8
#: addons/web_calendar/static/src/xml/web_calendar.xml:9
#: addons/web_calendar/static/src/js/calendar.js:168
msgid "Date"
msgstr "التاريخ"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:172
msgid "Year"
msgstr "السنة"
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:5
#: addons/web_calendar/static/src/xml/web_calendar.xml:6
msgid "&nbsp;"
msgstr "&nbsp;"

View File

@ -14,131 +14,131 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-07-03 05:55+0000\n"
"X-Generator: Launchpad (build 15531)\n"
"X-Launchpad-Export-Date: 2012-08-28 07:23+0000\n"
"X-Generator: Launchpad (build 15864)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:12
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr "Календар"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:73
#: addons/web_calendar/static/src/js/calendar.js:70
msgid "Filter"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:139
msgid "Today"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:140
msgid "Day"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:141
msgid "Week"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:142
msgid "Month"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:143
msgid "New event"
msgstr ""
msgstr "Филтър"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:144
msgid "Save"
msgstr ""
msgid "Today"
msgstr "Днес"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:145
msgid "Cancel"
msgstr ""
msgid "Day"
msgstr "Ден"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:146
msgid "Details"
msgstr ""
msgid "Week"
msgstr "Седмица"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:147
msgid "Edit"
msgstr ""
msgid "Month"
msgstr "Месец"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:148
msgid "Delete"
msgstr ""
msgid "New event"
msgstr "Ново събитие"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:149
msgid "Save"
msgstr "Запис"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:150
msgid "Event will be deleted permanently, are you sure?"
msgstr ""
msgid "Cancel"
msgstr "Отказ"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:151
#: addons/web_calendar/static/src/js/calendar.js:164
msgid "Description"
msgstr ""
msgid "Details"
msgstr "Детайли"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:152
msgid "Time period"
msgstr ""
msgid "Edit"
msgstr "Редакция"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:153
msgid "Full day"
msgstr ""
msgid "Delete"
msgstr "Изтриване"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:155
msgid "Event will be deleted permanently, are you sure?"
msgstr "Събитието ще бъде окончателно изтрито, сигурни ли сте?"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:156
msgid "Do you want to edit the whole set of repeated events?"
msgstr ""
#: addons/web_calendar/static/src/js/calendar.js:169
msgid "Description"
msgstr "Описание"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:157
msgid "Repeat event"
msgstr ""
msgid "Time period"
msgstr "Времеви период"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:158
msgid "Disabled"
msgstr ""
msgid "Full day"
msgstr "Цял ден"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:159
msgid "Enabled"
msgstr ""
#: addons/web_calendar/static/src/js/calendar.js:161
msgid "Do you want to edit the whole set of repeated events?"
msgstr "Искатели да редактирате всички повтарящи се събития?"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:162
#: addons/web_calendar/static/src/js/calendar.js:170
msgid "Agenda"
msgstr ""
msgid "Repeat event"
msgstr "Повтаряне на събитие"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:163
msgid "Date"
msgstr ""
msgid "Disabled"
msgstr "Изключено"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:164
msgid "Enabled"
msgstr "Включено"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:167
msgid "Year"
msgstr ""
#: addons/web_calendar/static/src/js/calendar.js:175
msgid "Agenda"
msgstr "Дневен ред"
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:8
#: addons/web_calendar/static/src/xml/web_calendar.xml:9
#: addons/web_calendar/static/src/js/calendar.js:168
msgid "Date"
msgstr "Дата"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:172
msgid "Year"
msgstr "Година"
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:5
#: addons/web_calendar/static/src/xml/web_calendar.xml:6
msgid "&nbsp;"
msgstr ""
msgstr "&nbsp;"
#~ msgid "Navigator"
#~ msgstr "Навигатор"

View File

@ -14,129 +14,129 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-07-03 05:55+0000\n"
"X-Generator: Launchpad (build 15531)\n"
"X-Launchpad-Export-Date: 2012-08-28 07:23+0000\n"
"X-Generator: Launchpad (build 15864)\n"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:12
#: addons/web_calendar/static/src/js/calendar.js:11
msgid "Calendar"
msgstr "পুঞ্জিকা"
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:73
#: addons/web_calendar/static/src/js/calendar.js:70
msgid "Filter"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:139
#: addons/web_calendar/static/src/js/calendar.js:144
msgid "Today"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:140
#: addons/web_calendar/static/src/js/calendar.js:145
msgid "Day"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:141
#: addons/web_calendar/static/src/js/calendar.js:146
msgid "Week"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:142
#: addons/web_calendar/static/src/js/calendar.js:147
msgid "Month"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:143
#: addons/web_calendar/static/src/js/calendar.js:148
msgid "New event"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:144
#: addons/web_calendar/static/src/js/calendar.js:149
msgid "Save"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:145
#: addons/web_calendar/static/src/js/calendar.js:150
msgid "Cancel"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:146
#: addons/web_calendar/static/src/js/calendar.js:151
msgid "Details"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:147
#: addons/web_calendar/static/src/js/calendar.js:152
msgid "Edit"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:148
#: addons/web_calendar/static/src/js/calendar.js:153
msgid "Delete"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:150
#: addons/web_calendar/static/src/js/calendar.js:155
msgid "Event will be deleted permanently, are you sure?"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:151
#: addons/web_calendar/static/src/js/calendar.js:164
#: addons/web_calendar/static/src/js/calendar.js:156
#: addons/web_calendar/static/src/js/calendar.js:169
msgid "Description"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:152
#: addons/web_calendar/static/src/js/calendar.js:157
msgid "Time period"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:153
#: addons/web_calendar/static/src/js/calendar.js:158
msgid "Full day"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:156
#: addons/web_calendar/static/src/js/calendar.js:161
msgid "Do you want to edit the whole set of repeated events?"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:157
#: addons/web_calendar/static/src/js/calendar.js:162
msgid "Repeat event"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:158
#: addons/web_calendar/static/src/js/calendar.js:163
msgid "Disabled"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:159
#: addons/web_calendar/static/src/js/calendar.js:164
msgid "Enabled"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:162
#: addons/web_calendar/static/src/js/calendar.js:170
#: addons/web_calendar/static/src/js/calendar.js:167
#: addons/web_calendar/static/src/js/calendar.js:175
msgid "Agenda"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:163
#: addons/web_calendar/static/src/js/calendar.js:168
msgid "Date"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/js/calendar.js:167
#: addons/web_calendar/static/src/js/calendar.js:172
msgid "Year"
msgstr ""
#. openerp-web
#: addons/web_calendar/static/src/xml/web_calendar.xml:8
#: addons/web_calendar/static/src/xml/web_calendar.xml:9
#: addons/web_calendar/static/src/xml/web_calendar.xml:5
#: addons/web_calendar/static/src/xml/web_calendar.xml:6
msgid "&nbsp;"
msgstr "&nbsp;"

Some files were not shown because too many files have changed in this diff Show More