diff --git a/addons/base/__openerp__.py b/addons/base/__openerp__.py
index 2c2d990364d..9cf8a6c893e 100644
--- a/addons/base/__openerp__.py
+++ b/addons/base/__openerp__.py
@@ -6,8 +6,11 @@
'js' : [
"static/lib/datejs/date-en-US.js",
"static/lib/jquery/jquery-1.6.2.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.contextmenu/jquery.contextmenu.r2.packed.js",
+ "static/lib/jquery.blockUI/jquery.blockUI.js",
"static/lib/jquery.superfish/js/hoverIntent.js",
"static/lib/jquery.superfish/js/superfish.js",
"static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js",
@@ -17,12 +20,13 @@
"static/lib/qweb/qweb2.js",
"static/lib/underscore/underscore.js",
"static/lib/underscore/underscore.string.js",
- "static/src/js/base.js",
+ "static/src/js/boot.js",
+ "static/src/js/core.js",
+ "static/src/js/dates.js",
"static/src/js/chrome.js",
- "static/src/js/controller.js",
"static/src/js/views.js",
"static/src/js/data.js",
- "static/src/js/dates.js",
+ "static/src/js/data_export.js",
"static/src/js/form.js",
"static/src/js/list.js",
"static/src/js/list-editable.js",
@@ -34,5 +38,6 @@
"static/lib/jquery.ui/css/smoothness/jquery-ui-1.8.9.custom.css",
"static/lib/jquery.ui.notify/css/ui.notify.css",
"static/src/css/base.css",
+ "static/src/css/data_export.css",
],
}
diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py
index 15d0b9951c7..6f63e5c7e24 100644
--- a/addons/base/controllers/main.py
+++ b/addons/base/controllers/main.py
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
+
import base64, glob, os, re
from xml.etree import ElementTree
from cStringIO import StringIO
+import operator
import simplejson
import openerpweb
@@ -10,6 +12,8 @@ import openerpweb.ast
import openerpweb.nonliterals
import cherrypy
+import xmlrpclib
+import csv
# Should move to openerpweb.Xml2Json
class Xml2Json:
@@ -151,16 +155,86 @@ class Database(openerpweb.Controller):
_cp_path = "/base/database"
@openerpweb.jsonrequest
- def get_databases_list(self, req):
+ def get_list(self, req):
proxy = req.session.proxy("db")
dbs = proxy.list()
h = req.httprequest.headers['Host'].split(':')[0]
d = h.split('.')[0]
- r = cherrypy.config['openerp.dbfilter'].replace('%h',h).replace('%d',d)
- print "h,d",h,d,r
- dbs = [i for i in dbs if re.match(r,i)]
+ r = cherrypy.config['openerp.dbfilter'].replace('%h', h).replace('%d', d)
+ dbs = [i for i in dbs if re.match(r, i)]
return {"db_list": dbs}
+ @openerpweb.jsonrequest
+ def progress(self, req, password, id):
+ return req.session.proxy('db').get_progress(password, id)
+
+ @openerpweb.jsonrequest
+ def create(self, req, fields):
+
+ params = dict(map(operator.itemgetter('name', 'value'), fields))
+ create_attrs = operator.itemgetter(
+ 'super_admin_pwd', 'db_name', 'demo_data', 'db_lang', 'create_admin_pwd')(
+ params)
+
+ try:
+ return req.session.proxy("db").create(*create_attrs)
+ except xmlrpclib.Fault, e:
+ if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
+ return {'error': e.faultCode, 'title': 'Create Database'}
+ return {'error': 'Could not create database !', 'title': 'Create Database'}
+
+ @openerpweb.jsonrequest
+ def drop(self, req, fields):
+ password, db = operator.itemgetter(
+ 'drop_pwd', 'drop_db')(
+ dict(map(operator.itemgetter('name', 'value'), fields)))
+
+ try:
+ return req.session.proxy("db").drop(password, db)
+ except xmlrpclib.Fault, e:
+ if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
+ return {'error': e.faultCode, 'title': 'Drop Database'}
+ return {'error': 'Could not drop database !', 'title': 'Drop Database'}
+
+ @openerpweb.httprequest
+ def backup(self, req, backup_db, backup_pwd, token):
+ try:
+ db_dump = base64.decodestring(
+ req.session.proxy("db").dump(backup_pwd, backup_db))
+ cherrypy.response.headers['Content-Type'] = "application/octet-stream; charset=binary"
+ cherrypy.response.headers['Content-Disposition'] = 'attachment; filename="' + backup_db + '.dump"'
+ cherrypy.response.cookie['fileToken'] = token
+ cherrypy.response.cookie['fileToken']['path'] = '/'
+ return db_dump
+ except xmlrpclib.Fault, e:
+ if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
+ return 'Backup Database|' + e.faultCode
+ return 'Backup Database|Could not generate database backup'
+
+ @openerpweb.httprequest
+ def restore(self, req, db_file, restore_pwd, new_db):
+ try:
+ data = base64.encodestring(db_file.file.read())
+ req.session.proxy("db").restore(restore_pwd, new_db, data)
+ return ''
+ except xmlrpclib.Fault, e:
+ if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
+ raise cherrypy.HTTPError(403)
+
+ raise cherrypy.HTTPError()
+
+ @openerpweb.jsonrequest
+ def change_password(self, req, fields):
+ old_password, new_password = operator.itemgetter(
+ 'old_pwd', 'new_pwd')(
+ dict(map(operator.itemgetter('name', 'value'), fields)))
+ try:
+ return req.session.proxy("db").change_admin_password(old_password, new_password)
+ except xmlrpclib.Fault, e:
+ if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
+ return {'error': e.faultCode, 'title': 'Change Password'}
+ return {'error': 'Error, password not changed !', 'title': 'Change Password'}
+
class Session(openerpweb.Controller):
_cp_path = "/base/session"
@@ -178,6 +252,16 @@ class Session(openerpweb.Controller):
return req.session.model('ir.ui.view_sc').get_sc(req.session._uid, "ir.ui.menu",
req.session.eval_context(req.context))
+ @openerpweb.jsonrequest
+ def get_lang_list(self, req):
+ try:
+ return {
+ 'lang_list': (req.session.proxy("db").list_lang() or []),
+ 'error': ""
+ }
+ except Exception, e:
+ return {"error": e, "title": "Languages"}
+
@openerpweb.jsonrequest
def modules(self, req):
# TODO query server for installed web modules
@@ -222,7 +306,7 @@ class Session(openerpweb.Controller):
context, domain = eval_context_and_domain(req.session,
openerpweb.nonliterals.CompoundContext(*(contexts or [])),
openerpweb.nonliterals.CompoundDomain(*(domains or [])))
-
+
group_by_sequence = []
for candidate in (group_by_seq or []):
ctx = req.session.eval_context(candidate, context)
@@ -233,7 +317,7 @@ class Session(openerpweb.Controller):
group_by_sequence.append(group_by)
else:
group_by_sequence.extend(group_by)
-
+
return {
'context': context,
'domain': domain,
@@ -246,7 +330,7 @@ class Session(openerpweb.Controller):
This method store an action object in the session object and returns an integer
identifying that action. The method get_session_action() can be used to get
back the action.
-
+
:param the_action: The action to save in the session.
:type the_action: anything
:return: A key identifying the saved action.
@@ -269,7 +353,7 @@ class Session(openerpweb.Controller):
"""
Gets back a previously saved action. This method can return None if the action
was saved since too much time (this case should be handled in a smart way).
-
+
:param key: The key given by save_session_action()
:type key: integer
:return: The saved action or None.
@@ -408,7 +492,7 @@ class Menu(openerpweb.Controller):
menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context)
menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']}
menu_items.append(menu_root)
-
+
# make a tree using parent_id
menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items)
for menu_item in menu_items:
@@ -515,7 +599,7 @@ class DataSet(openerpweb.Controller):
record_map = dict((record['id'], record) for record in records)
return [record_map[id] for id in ids if record_map.get(id)]
-
+
@openerpweb.jsonrequest
def load(self, req, model, id, fields):
m = req.session.model(model)
@@ -678,7 +762,7 @@ class View(openerpweb.Controller):
except ValueError:
# not a literal
return openerpweb.nonliterals.Domain(session, domain)
-
+
def parse_context(self, context, session):
""" Parses an arbitrary string containing a context, transforms it
to either a literal context or a :class:`openerpweb.nonliterals.Context`
@@ -906,3 +990,217 @@ class Action(openerpweb.Controller):
def run(self, req, action_id):
return clean_action(req.session.model('ir.actions.server').run(
[action_id], req.session.eval_context(req.context)), req.session)
+
+def export_csv(fields, result):
+ fp = StringIO()
+ writer = csv.writer(fp, quoting=csv.QUOTE_ALL)
+
+ writer.writerow(fields)
+
+ for data in result:
+ row = []
+ for d in data:
+ if isinstance(d, basestring):
+ d = d.replace('\n',' ').replace('\t',' ')
+ try:
+ d = d.encode('utf-8')
+ except:
+ pass
+ if d is False: d = None
+ row.append(d)
+ writer.writerow(row)
+
+ fp.seek(0)
+ data = fp.read()
+ fp.close()
+ return data
+
+def export_xls(fieldnames, table):
+ try:
+ import xlwt
+ except ImportError:
+ common.error(_('Import Error.'), _('Please install xlwt library to export to MS Excel.'))
+
+ workbook = xlwt.Workbook()
+ worksheet = workbook.add_sheet('Sheet 1')
+
+ for i, fieldname in enumerate(fieldnames):
+ worksheet.write(0, i, str(fieldname))
+ worksheet.col(i).width = 8000 # around 220 pixels
+
+ style = xlwt.easyxf('align: wrap yes')
+
+ for row_index, row in enumerate(table):
+ for cell_index, cell_value in enumerate(row):
+ cell_value = str(cell_value)
+ cell_value = re.sub("\r", " ", cell_value)
+ worksheet.write(row_index + 1, cell_index, cell_value, style)
+
+
+ fp = StringIO()
+ workbook.save(fp)
+ fp.seek(0)
+ data = fp.read()
+ fp.close()
+ #return data.decode('ISO-8859-1')
+ return unicode(data, 'utf-8', 'replace')
+
+class Export(View):
+ _cp_path = "/base/export"
+
+ def fields_get(self, req, model):
+ Model = req.session.model(model)
+ fields = Model.fields_get(False, req.session.eval_context(req.context))
+ return fields
+
+ @openerpweb.jsonrequest
+ def get_fields(self, req, model, prefix='', name= '', field_parent=None, params={}):
+ import_compat = params.get("import_compat", False)
+
+ fields = self.fields_get(req, model)
+ field_parent_type = params.get("parent_field_type",False)
+
+ if import_compat and field_parent_type and field_parent_type == "many2one":
+ fields = {}
+
+ fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
+ records = []
+ fields_order = fields.keys()
+ fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', '')))
+
+ for index, field in enumerate(fields_order):
+ value = fields[field]
+ record = {}
+ if import_compat and value.get('readonly', False):
+ ok = False
+ for sl in value.get('states', {}).values():
+ for s in sl:
+ ok = ok or (s==['readonly',False])
+ if not ok: continue
+
+ id = prefix + (prefix and '/'or '') + field
+ nm = name + (name and '/' or '') + value['string']
+ record.update(id=id, string= nm, action='javascript: void(0)',
+ target=None, icon=None, children=[], field_type=value.get('type',False), required=value.get('required', False))
+ records.append(record)
+
+ if len(nm.split('/')) < 3 and value.get('relation', False):
+ if import_compat:
+ ref = value.pop('relation')
+ cfields = self.fields_get(req, ref)
+ if (value['type'] == 'many2many'):
+ record['children'] = []
+ record['params'] = {'model': ref, 'prefix': id, 'name': nm}
+
+ elif value['type'] == 'many2one':
+ record['children'] = [id + '/id', id + '/.id']
+ record['params'] = {'model': ref, 'prefix': id, 'name': nm}
+
+ else:
+ cfields_order = cfields.keys()
+ cfields_order.sort(lambda x,y: -cmp(cfields[x].get('string', ''), cfields[y].get('string', '')))
+ children = []
+ for j, fld in enumerate(cfields_order):
+ cid = id + '/' + fld
+ cid = cid.replace(' ', '_')
+ children.append(cid)
+ record['children'] = children or []
+ record['params'] = {'model': ref, 'prefix': id, 'name': nm}
+ else:
+ ref = value.pop('relation')
+ cfields = self.fields_get(req, ref)
+ cfields_order = cfields.keys()
+ cfields_order.sort(lambda x,y: -cmp(cfields[x].get('string', ''), cfields[y].get('string', '')))
+ children = []
+ for j, fld in enumerate(cfields_order):
+ cid = id + '/' + fld
+ cid = cid.replace(' ', '_')
+ children.append(cid)
+ record['children'] = children or []
+ record['params'] = {'model': ref, 'prefix': id, 'name': nm}
+
+ records.reverse()
+ return records
+
+ @openerpweb.jsonrequest
+ def save_export_lists(self, req, name, model, field_list):
+ result = {'resource':model, 'name':name, 'export_fields': []}
+ for field in field_list:
+ result['export_fields'].append((0, 0, {'name': field}))
+ return req.session.model("ir.exports").create(result, req.session.eval_context(req.context))
+
+ @openerpweb.jsonrequest
+ def exist_export_lists(self, req, model):
+ export_model = req.session.model("ir.exports")
+ return export_model.read(export_model.search([('resource', '=', model)]), ['name'])
+
+ @openerpweb.jsonrequest
+ def delete_export(self, req, export_id):
+ req.session.model("ir.exports").unlink(export_id, req.session.eval_context(req.context))
+ return True
+
+ @openerpweb.jsonrequest
+ def namelist(self,req, model, export_id):
+
+ result = self.get_data(req, model, req.session.eval_context(req.context))
+ ir_export_obj = req.session.model("ir.exports")
+ ir_export_line_obj = req.session.model("ir.exports.line")
+
+ field = ir_export_obj.read(export_id)
+ fields = ir_export_line_obj.read(field['export_fields'])
+
+ name_list = {}
+ [name_list.update({field['name']: result.get(field['name'])}) for field in fields]
+ return name_list
+
+ def get_data(self, req, model, context=None):
+ ids = []
+ context = context or {}
+ fields_data = {}
+ proxy = req.session.model(model)
+ fields = self.fields_get(req, model)
+ if not ids:
+ f1 = proxy.fields_view_get(False, 'tree', context)['fields']
+ f2 = proxy.fields_view_get(False, 'form', context)['fields']
+
+ fields = dict(f1)
+ fields.update(f2)
+ fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
+
+ def rec(fields):
+ _fields = {'id': 'ID' , '.id': 'Database ID' }
+ def model_populate(fields, prefix_node='', prefix=None, prefix_value='', level=2):
+ fields_order = fields.keys()
+ fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', '')))
+
+ for field in fields_order:
+ fields_data[prefix_node+field] = fields[field]
+ if prefix_node:
+ fields_data[prefix_node + field]['string'] = '%s%s' % (prefix_value, fields_data[prefix_node + field]['string'])
+ st_name = fields[field]['string'] or field
+ _fields[prefix_node+field] = st_name
+ if fields[field].get('relation', False) and level>0:
+ fields2 = self.fields_get(req, fields[field]['relation'])
+ fields2.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}})
+ model_populate(fields2, prefix_node+field+'/', None, st_name+'/', level-1)
+ model_populate(fields)
+ return _fields
+ return rec(fields)
+
+ @openerpweb.jsonrequest
+ def export_data(self, req, model, fields, ids, domain, import_compat=False, export_format="csv", context=None):
+ context = req.session.eval_context(req.context)
+ modle_obj = req.session.model(model)
+ ids = ids or modle_obj.search(domain, context=context)
+
+ field = fields.keys()
+ result = modle_obj.export_data(ids, field , context).get('datas',[])
+
+ if not import_compat:
+ field = [val.strip() for val in fields.values()]
+
+ if export_format == 'xls':
+ return export_xls(field, result)
+ else:
+ return export_csv(field, result)
+
diff --git a/addons/base/static/lib/jquery.blockUI/jquery.blockUI.js b/addons/base/static/lib/jquery.blockUI/jquery.blockUI.js
new file mode 100644
index 00000000000..04db883c76e
--- /dev/null
+++ b/addons/base/static/lib/jquery.blockUI/jquery.blockUI.js
@@ -0,0 +1,499 @@
+/*!
+ * jQuery blockUI plugin
+ * Version 2.39 (23-MAY-2011)
+ * @requires jQuery v1.2.3 or later
+ *
+ * Examples at: http://malsup.com/jquery/block/
+ * Copyright (c) 2007-2010 M. Alsup
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Thanks to Amir-Hossein Sobhi for some excellent contributions!
+ */
+
+;(function($) {
+
+if (/1\.(0|1|2)\.(0|1|2)/.test($.fn.jquery) || /^1.1/.test($.fn.jquery)) {
+ alert('blockUI requires jQuery v1.2.3 or later! You are using v' + $.fn.jquery);
+ return;
+}
+
+$.fn._fadeIn = $.fn.fadeIn;
+
+var noOp = function() {};
+
+// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
+// retarded userAgent strings on Vista)
+var mode = document.documentMode || 0;
+var setExpr = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8);
+var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent) && !mode;
+
+// global $ methods for blocking/unblocking the entire page
+$.blockUI = function(opts) { install(window, opts); };
+$.unblockUI = function(opts) { remove(window, opts); };
+
+// convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
+$.growlUI = function(title, message, timeout, onClose) {
+ var $m = $('
');
+ if (title) $m.append('
'+title+'
');
+ if (message) $m.append('
'+message+'
');
+ if (timeout == undefined) timeout = 3000;
+ $.blockUI({
+ message: $m, fadeIn: 700, fadeOut: 1000, centerY: false,
+ timeout: timeout, showOverlay: false,
+ onUnblock: onClose,
+ css: $.blockUI.defaults.growlCSS
+ });
+};
+
+// plugin method for blocking element content
+$.fn.block = function(opts) {
+ return this.unblock({ fadeOut: 0 }).each(function() {
+ if ($.css(this,'position') == 'static')
+ this.style.position = 'relative';
+ if ($.browser.msie)
+ this.style.zoom = 1; // force 'hasLayout'
+ install(this, opts);
+ });
+};
+
+// plugin method for unblocking element content
+$.fn.unblock = function(opts) {
+ return this.each(function() {
+ remove(this, opts);
+ });
+};
+
+$.blockUI.version = 2.39; // 2nd generation blocking at no extra cost!
+
+// override these in your code to change the default behavior and style
+$.blockUI.defaults = {
+ // message displayed when blocking (use null for no message)
+ message: '
Please wait...
',
+
+ title: null, // title string; only used when theme == true
+ draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded)
+
+ theme: false, // set to true to use with jQuery UI themes
+
+ // styles for the message when blocking; if you wish to disable
+ // these and use an external stylesheet then do this in your code:
+ // $.blockUI.defaults.css = {};
+ css: {
+ padding: 0,
+ margin: 0,
+ width: '30%',
+ top: '40%',
+ left: '35%',
+ textAlign: 'center',
+ color: '#000',
+ border: '3px solid #aaa',
+ backgroundColor:'#fff',
+ cursor: 'wait'
+ },
+
+ // minimal style set used when themes are used
+ themedCSS: {
+ width: '30%',
+ top: '40%',
+ left: '35%'
+ },
+
+ // styles for the overlay
+ overlayCSS: {
+ backgroundColor: '#000',
+ opacity: 0.6,
+ cursor: 'wait'
+ },
+
+ // styles applied when using $.growlUI
+ growlCSS: {
+ width: '350px',
+ top: '10px',
+ left: '',
+ right: '10px',
+ border: 'none',
+ padding: '5px',
+ opacity: 0.6,
+ cursor: 'default',
+ color: '#fff',
+ backgroundColor: '#000',
+ '-webkit-border-radius': '10px',
+ '-moz-border-radius': '10px',
+ 'border-radius': '10px'
+ },
+
+ // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
+ // (hat tip to Jorge H. N. de Vasconcelos)
+ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
+
+ // force usage of iframe in non-IE browsers (handy for blocking applets)
+ forceIframe: false,
+
+ // z-index for the blocking overlay
+ baseZ: 1000,
+
+ // set these to true to have the message automatically centered
+ centerX: true, // <-- only effects element blocking (page block controlled via css above)
+ centerY: true,
+
+ // allow body element to be stetched in ie6; this makes blocking look better
+ // on "short" pages. disable if you wish to prevent changes to the body height
+ allowBodyStretch: true,
+
+ // enable if you want key and mouse events to be disabled for content that is blocked
+ bindEvents: true,
+
+ // be default blockUI will supress tab navigation from leaving blocking content
+ // (if bindEvents is true)
+ constrainTabKey: true,
+
+ // fadeIn time in millis; set to 0 to disable fadeIn on block
+ fadeIn: 200,
+
+ // fadeOut time in millis; set to 0 to disable fadeOut on unblock
+ fadeOut: 400,
+
+ // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
+ timeout: 0,
+
+ // disable if you don't want to show the overlay
+ showOverlay: true,
+
+ // if true, focus will be placed in the first available input field when
+ // page blocking
+ focusInput: true,
+
+ // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
+ applyPlatformOpacityRules: true,
+
+ // callback method invoked when fadeIn has completed and blocking message is visible
+ onBlock: null,
+
+ // callback method invoked when unblocking has completed; the callback is
+ // passed the element that has been unblocked (which is the window object for page
+ // blocks) and the options that were passed to the unblock call:
+ // onUnblock(element, options)
+ onUnblock: null,
+
+ // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
+ quirksmodeOffsetHack: 4,
+
+ // class name of the message block
+ blockMsgClass: 'blockMsg'
+};
+
+// private data and functions follow...
+
+var pageBlock = null;
+var pageBlockEls = [];
+
+function install(el, opts) {
+ var full = (el == window);
+ var msg = opts && opts.message !== undefined ? opts.message : undefined;
+ opts = $.extend({}, $.blockUI.defaults, opts || {});
+ opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
+ var css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
+ var themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
+ msg = msg === undefined ? opts.message : msg;
+
+ // remove the current block (if there is one)
+ if (full && pageBlock)
+ remove(window, {fadeOut:0});
+
+ // if an existing element is being used as the blocking content then we capture
+ // its current place in the DOM (and current display style) so we can restore
+ // it when we unblock
+ if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
+ var node = msg.jquery ? msg[0] : msg;
+ var data = {};
+ $(el).data('blockUI.history', data);
+ data.el = node;
+ data.parent = node.parentNode;
+ data.display = node.style.display;
+ data.position = node.style.position;
+ if (data.parent)
+ data.parent.removeChild(node);
+ }
+
+ $(el).data('blockUI.onUnblock', opts.onUnblock);
+ var z = opts.baseZ;
+
+ // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
+ // layer1 is the iframe layer which is used to supress bleed through of underlying content
+ // layer2 is the overlay layer which has opacity and a wait cursor (by default)
+ // layer3 is the message content that is displayed while blocking
+
+ var lyr1 = ($.browser.msie || opts.forceIframe)
+ ? $('')
+ : $('');
+
+ var lyr2 = opts.theme
+ ? $('')
+ : $('');
+
+ var lyr3, s;
+ if (opts.theme && full) {
+ s = '
' +
+ '
'+(opts.title || ' ')+'
' +
+ '' +
+ '
';
+ }
+ else if (opts.theme) {
+ s = '
' +
+ '
'+(opts.title || ' ')+'
' +
+ '' +
+ '
';
+ }
+ else if (full) {
+ s = '';
+ }
+ else {
+ s = '';
+ }
+ lyr3 = $(s);
+
+ // if we have a message, style it
+ if (msg) {
+ if (opts.theme) {
+ lyr3.css(themedCSS);
+ lyr3.addClass('ui-widget-content');
+ }
+ else
+ lyr3.css(css);
+ }
+
+ // style the overlay
+ if (!opts.theme && (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform))))
+ lyr2.css(opts.overlayCSS);
+ lyr2.css('position', full ? 'fixed' : 'absolute');
+
+ // make iframe layer transparent in IE
+ if ($.browser.msie || opts.forceIframe)
+ lyr1.css('opacity',0.0);
+
+ //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
+ var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
+ $.each(layers, function() {
+ this.appendTo($par);
+ });
+
+ if (opts.theme && opts.draggable && $.fn.draggable) {
+ lyr3.draggable({
+ handle: '.ui-dialog-titlebar',
+ cancel: 'li'
+ });
+ }
+
+ // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
+ var expr = setExpr && (!$.boxModel || $('object,embed', full ? null : el).length > 0);
+ if (ie6 || expr) {
+ // give body 100% height
+ if (full && opts.allowBodyStretch && $.boxModel)
+ $('html,body').css('height','100%');
+
+ // fix ie6 issue when blocked element has a border width
+ if ((ie6 || !$.boxModel) && !full) {
+ var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
+ var fixT = t ? '(0 - '+t+')' : 0;
+ var fixL = l ? '(0 - '+l+')' : 0;
+ }
+
+ // simulate fixed position
+ $.each([lyr1,lyr2,lyr3], function(i,o) {
+ var s = o[0].style;
+ s.position = 'absolute';
+ if (i < 2) {
+ full ? s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"')
+ : s.setExpression('height','this.parentNode.offsetHeight + "px"');
+ full ? s.setExpression('width','jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"')
+ : s.setExpression('width','this.parentNode.offsetWidth + "px"');
+ if (fixL) s.setExpression('left', fixL);
+ if (fixT) s.setExpression('top', fixT);
+ }
+ else if (opts.centerY) {
+ if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
+ s.marginTop = 0;
+ }
+ else if (!opts.centerY && full) {
+ var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0;
+ var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
+ s.setExpression('top',expression);
+ }
+ });
+ }
+
+ // show the message
+ if (msg) {
+ if (opts.theme)
+ lyr3.find('.ui-widget-content').append(msg);
+ else
+ lyr3.append(msg);
+ if (msg.jquery || msg.nodeType)
+ $(msg).show();
+ }
+
+ if (($.browser.msie || opts.forceIframe) && opts.showOverlay)
+ lyr1.show(); // opacity is zero
+ if (opts.fadeIn) {
+ var cb = opts.onBlock ? opts.onBlock : noOp;
+ var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
+ var cb2 = msg ? cb : noOp;
+ if (opts.showOverlay)
+ lyr2._fadeIn(opts.fadeIn, cb1);
+ if (msg)
+ lyr3._fadeIn(opts.fadeIn, cb2);
+ }
+ else {
+ if (opts.showOverlay)
+ lyr2.show();
+ if (msg)
+ lyr3.show();
+ if (opts.onBlock)
+ opts.onBlock();
+ }
+
+ // bind key and mouse events
+ bind(1, el, opts);
+
+ if (full) {
+ pageBlock = lyr3[0];
+ pageBlockEls = $(':input:enabled:visible',pageBlock);
+ if (opts.focusInput)
+ setTimeout(focus, 20);
+ }
+ else
+ center(lyr3[0], opts.centerX, opts.centerY);
+
+ if (opts.timeout) {
+ // auto-unblock
+ var to = setTimeout(function() {
+ full ? $.unblockUI(opts) : $(el).unblock(opts);
+ }, opts.timeout);
+ $(el).data('blockUI.timeout', to);
+ }
+};
+
+// remove the block
+function remove(el, opts) {
+ var full = (el == window);
+ var $el = $(el);
+ var data = $el.data('blockUI.history');
+ var to = $el.data('blockUI.timeout');
+ if (to) {
+ clearTimeout(to);
+ $el.removeData('blockUI.timeout');
+ }
+ opts = $.extend({}, $.blockUI.defaults, opts || {});
+ bind(0, el, opts); // unbind events
+
+ if (opts.onUnblock === null) {
+ opts.onUnblock = $el.data('blockUI.onUnblock');
+ $el.removeData('blockUI.onUnblock');
+ }
+
+ var els;
+ if (full) // crazy selector to handle odd field errors in ie6/7
+ els = $('body').children().filter('.blockUI').add('body > .blockUI');
+ else
+ els = $('.blockUI', el);
+
+ if (full)
+ pageBlock = pageBlockEls = null;
+
+ if (opts.fadeOut) {
+ els.fadeOut(opts.fadeOut);
+ setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut);
+ }
+ else
+ reset(els, data, opts, el);
+};
+
+// move blocking element back into the DOM where it started
+function reset(els,data,opts,el) {
+ els.each(function(i,o) {
+ // remove via DOM calls so we don't lose event handlers
+ if (this.parentNode)
+ this.parentNode.removeChild(this);
+ });
+
+ if (data && data.el) {
+ data.el.style.display = data.display;
+ data.el.style.position = data.position;
+ if (data.parent)
+ data.parent.appendChild(data.el);
+ $(el).removeData('blockUI.history');
+ }
+
+ if (typeof opts.onUnblock == 'function')
+ opts.onUnblock(el,opts);
+};
+
+// bind/unbind the handler
+function bind(b, el, opts) {
+ var full = el == window, $el = $(el);
+
+ // don't bother unbinding if there is nothing to unbind
+ if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
+ return;
+ if (!full)
+ $el.data('blockUI.isBlocked', b);
+
+ // don't bind events when overlay is not in use or if bindEvents is false
+ if (!opts.bindEvents || (b && !opts.showOverlay))
+ return;
+
+ // bind anchors and inputs for mouse and key events
+ var events = 'mousedown mouseup keydown keypress';
+ b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler);
+
+// former impl...
+// var $e = $('a,:input');
+// b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
+};
+
+// event handler to suppress keyboard/mouse events when blocking
+function handler(e) {
+ // allow tab navigation (conditionally)
+ if (e.keyCode && e.keyCode == 9) {
+ if (pageBlock && e.data.constrainTabKey) {
+ var els = pageBlockEls;
+ var fwd = !e.shiftKey && e.target === els[els.length-1];
+ var back = e.shiftKey && e.target === els[0];
+ if (fwd || back) {
+ setTimeout(function(){focus(back)},10);
+ return false;
+ }
+ }
+ }
+ var opts = e.data;
+ // allow events within the message content
+ if ($(e.target).parents('div.' + opts.blockMsgClass).length > 0)
+ return true;
+
+ // allow events for content that is not being blocked
+ return $(e.target).parents().children().filter('div.blockUI').length == 0;
+};
+
+function focus(back) {
+ if (!pageBlockEls)
+ return;
+ var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
+ if (e)
+ e.focus();
+};
+
+function center(el, x, y) {
+ var p = el.parentNode, s = el.style;
+ var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
+ var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
+ if (x) s.left = l > 0 ? (l+'px') : '0';
+ if (y) s.top = t > 0 ? (t+'px') : '0';
+};
+
+function sz(el, p) {
+ return parseInt($.css(el,p))||0;
+};
+
+})(jQuery);
diff --git a/addons/base/static/lib/jquery.form/jquery.form.js b/addons/base/static/lib/jquery.form/jquery.form.js
new file mode 100644
index 00000000000..66ac5142238
--- /dev/null
+++ b/addons/base/static/lib/jquery.form/jquery.form.js
@@ -0,0 +1,911 @@
+/*!
+ * jQuery Form Plugin
+ * version: 2.83 (11-JUL-2011)
+ * @requires jQuery v1.3.2 or later
+ *
+ * Examples and documentation at: http://malsup.com/jquery/form/
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ */
+;(function($) {
+
+/*
+ Usage Note:
+ -----------
+ Do not use both ajaxSubmit and ajaxForm on the same form. These
+ functions are intended to be exclusive. Use ajaxSubmit if you want
+ to bind your own submit handler to the form. For example,
+
+ $(document).ready(function() {
+ $('#myForm').bind('submit', function(e) {
+ e.preventDefault(); // <-- important
+ $(this).ajaxSubmit({
+ target: '#output'
+ });
+ });
+ });
+
+ Use ajaxForm when you want the plugin to manage all the event binding
+ for you. For example,
+
+ $(document).ready(function() {
+ $('#myForm').ajaxForm({
+ target: '#output'
+ });
+ });
+
+ When using ajaxForm, the ajaxSubmit function will be invoked for you
+ at the appropriate time.
+*/
+
+/**
+ * ajaxSubmit() provides a mechanism for immediately submitting
+ * an HTML form using AJAX.
+ */
+$.fn.ajaxSubmit = function(options) {
+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
+ if (!this.length) {
+ log('ajaxSubmit: skipping submit process - no element selected');
+ return this;
+ }
+
+ var method, action, url, $form = this;
+
+ if (typeof options == 'function') {
+ options = { success: options };
+ }
+
+ method = this.attr('method');
+ action = this.attr('action');
+ url = (typeof action === 'string') ? $.trim(action) : '';
+ url = url || window.location.href || '';
+ if (url) {
+ // clean url (don't include hash vaue)
+ url = (url.match(/^([^#]+)/)||[])[1];
+ }
+
+ options = $.extend(true, {
+ url: url,
+ success: $.ajaxSettings.success,
+ type: method || 'GET',
+ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
+ }, options);
+
+ // hook for manipulating the form data before it is extracted;
+ // convenient for use with rich editors like tinyMCE or FCKEditor
+ var veto = {};
+ this.trigger('form-pre-serialize', [this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
+ return this;
+ }
+
+ // provide opportunity to alter form data before it is serialized
+ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSerialize callback');
+ return this;
+ }
+
+ var n,v,a = this.formToArray(options.semantic);
+ if (options.data) {
+ options.extraData = options.data;
+ for (n in options.data) {
+ if(options.data[n] instanceof Array) {
+ for (var k in options.data[n]) {
+ a.push( { name: n, value: options.data[n][k] } );
+ }
+ }
+ else {
+ v = options.data[n];
+ v = $.isFunction(v) ? v() : v; // if value is fn, invoke it
+ a.push( { name: n, value: v } );
+ }
+ }
+ }
+
+ // give pre-submit callback an opportunity to abort the submit
+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSubmit callback');
+ return this;
+ }
+
+ // fire vetoable 'validate' event
+ this.trigger('form-submit-validate', [a, this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
+ return this;
+ }
+
+ var q = $.param(a);
+
+ if (options.type.toUpperCase() == 'GET') {
+ options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
+ options.data = null; // data is null for 'get'
+ }
+ else {
+ options.data = q; // data is the query string for 'post'
+ }
+
+ var callbacks = [];
+ if (options.resetForm) {
+ callbacks.push(function() { $form.resetForm(); });
+ }
+ if (options.clearForm) {
+ callbacks.push(function() { $form.clearForm(); });
+ }
+
+ // perform a load on the target only if dataType is not provided
+ if (!options.dataType && options.target) {
+ var oldSuccess = options.success || function(){};
+ callbacks.push(function(data) {
+ var fn = options.replaceTarget ? 'replaceWith' : 'html';
+ $(options.target)[fn](data).each(oldSuccess, arguments);
+ });
+ }
+ else if (options.success) {
+ callbacks.push(options.success);
+ }
+
+ options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
+ var context = options.context || options; // jQuery 1.4+ supports scope context
+ for (var i=0, max=callbacks.length; i < max; i++) {
+ callbacks[i].apply(context, [data, status, xhr || $form, $form]);
+ }
+ };
+
+ // are there files to upload?
+ var fileInputs = $('input:file', this).length > 0;
+ var mp = 'multipart/form-data';
+ var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
+
+ // options.iframe allows user to force iframe mode
+ // 06-NOV-09: now defaulting to iframe mode if file input is detected
+ if (options.iframe !== false && (fileInputs || options.iframe || multipart)) {
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+ if (options.closeKeepAlive) {
+ $.get(options.closeKeepAlive, function() { fileUpload(a); });
+ }
+ else {
+ fileUpload(a);
+ }
+ }
+ else {
+ // IE7 massage (see issue 57)
+ if ($.browser.msie && method == 'get') {
+ var ieMeth = $form[0].getAttribute('method');
+ if (typeof ieMeth === 'string')
+ options.type = ieMeth;
+ }
+ $.ajax(options);
+ }
+
+ // fire 'notify' event
+ this.trigger('form-submit-notify', [this, options]);
+ return this;
+
+
+ // private function for handling file uploads (hat tip to YAHOO!)
+ function fileUpload(a) {
+ var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
+ var useProp = !!$.fn.prop;
+
+ if (a) {
+ // ensure that every serialized input is still enabled
+ for (i=0; i < a.length; i++) {
+ el = $(form[a[i].name]);
+ el[ useProp ? 'prop' : 'attr' ]('disabled', false);
+ }
+ }
+
+ if ($(':input[name=submit],:input[id=submit]', form).length) {
+ // if there is an input with a name or id of 'submit' then we won't be
+ // able to invoke the submit fn on the form (at least not x-browser)
+ alert('Error: Form elements must not have name or id of "submit".');
+ return;
+ }
+
+ s = $.extend(true, {}, $.ajaxSettings, options);
+ s.context = s.context || s;
+ id = 'jqFormIO' + (new Date().getTime());
+ if (s.iframeTarget) {
+ $io = $(s.iframeTarget);
+ n = $io.attr('name');
+ if (n == null)
+ $io.attr('name', id);
+ else
+ id = n;
+ }
+ else {
+ $io = $('');
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+ }
+ io = $io[0];
+
+
+ xhr = { // mock object
+ aborted: 0,
+ responseText: null,
+ responseXML: null,
+ status: 0,
+ statusText: 'n/a',
+ getAllResponseHeaders: function() {},
+ getResponseHeader: function() {},
+ setRequestHeader: function() {},
+ abort: function(status) {
+ var e = (status === 'timeout' ? 'timeout' : 'aborted');
+ log('aborting upload... ' + e);
+ this.aborted = 1;
+ $io.attr('src', s.iframeSrc); // abort op in progress
+ xhr.error = e;
+ s.error && s.error.call(s.context, xhr, e, status);
+ g && $.event.trigger("ajaxError", [xhr, s, e]);
+ s.complete && s.complete.call(s.context, xhr, e);
+ }
+ };
+
+ g = s.global;
+ // trigger ajax global events so that activity/block indicators work like normal
+ if (g && ! $.active++) {
+ $.event.trigger("ajaxStart");
+ }
+ if (g) {
+ $.event.trigger("ajaxSend", [xhr, s]);
+ }
+
+ if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
+ if (s.global) {
+ $.active--;
+ }
+ return;
+ }
+ if (xhr.aborted) {
+ return;
+ }
+
+ // add submitting element to data if we know it
+ sub = form.clk;
+ if (sub) {
+ n = sub.name;
+ if (n && !sub.disabled) {
+ s.extraData = s.extraData || {};
+ s.extraData[n] = sub.value;
+ if (sub.type == "image") {
+ s.extraData[n+'.x'] = form.clk_x;
+ s.extraData[n+'.y'] = form.clk_y;
+ }
+ }
+ }
+
+ var CLIENT_TIMEOUT_ABORT = 1;
+ var SERVER_ABORT = 2;
+
+ function getDoc(frame) {
+ var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
+ return doc;
+ }
+
+ // take a breath so that pending repaints get some cpu time before the upload starts
+ function doSubmit() {
+ // make sure form attrs are set
+ var t = $form.attr('target'), a = $form.attr('action');
+
+ // update form attrs in IE friendly way
+ form.setAttribute('target',id);
+ if (!method) {
+ form.setAttribute('method', 'POST');
+ }
+ if (a != s.url) {
+ form.setAttribute('action', s.url);
+ }
+
+ // ie borks in some cases when setting encoding
+ if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
+ $form.attr({
+ encoding: 'multipart/form-data',
+ enctype: 'multipart/form-data'
+ });
+ }
+
+ // support timout
+ if (s.timeout) {
+ timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
+ }
+
+ // look for server aborts
+ function checkState() {
+ try {
+ var state = getDoc(io).readyState;
+ log('state = ' + state);
+ if (state.toLowerCase() == 'uninitialized')
+ setTimeout(checkState,50);
+ }
+ catch(e) {
+ log('Server abort: ' , e, ' (', e.name, ')');
+ cb(SERVER_ABORT);
+ timeoutHandle && clearTimeout(timeoutHandle);
+ timeoutHandle = undefined;
+ }
+ }
+
+ // add "extra" data to form if provided in options
+ var extraInputs = [];
+ try {
+ if (s.extraData) {
+ for (var n in s.extraData) {
+ extraInputs.push(
+ $('').attr('value',s.extraData[n])
+ .appendTo(form)[0]);
+ }
+ }
+
+ if (!s.iframeTarget) {
+ // add iframe to doc and submit the form
+ $io.appendTo('body');
+ io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
+ }
+ setTimeout(checkState,15);
+ form.submit();
+ }
+ finally {
+ // reset attrs and remove "extra" input elements
+ form.setAttribute('action',a);
+ if(t) {
+ form.setAttribute('target', t);
+ } else {
+ $form.removeAttr('target');
+ }
+ $(extraInputs).remove();
+ }
+ }
+
+ if (s.forceSync) {
+ doSubmit();
+ }
+ else {
+ setTimeout(doSubmit, 10); // this lets dom updates render
+ }
+
+ var data, doc, domCheckCount = 50, callbackProcessed;
+
+ function cb(e) {
+ if (xhr.aborted || callbackProcessed) {
+ return;
+ }
+ try {
+ doc = getDoc(io);
+ }
+ catch(ex) {
+ log('cannot access response document: ', ex);
+ e = SERVER_ABORT;
+ }
+ if (e === CLIENT_TIMEOUT_ABORT && xhr) {
+ xhr.abort('timeout');
+ return;
+ }
+ else if (e == SERVER_ABORT && xhr) {
+ xhr.abort('server abort');
+ return;
+ }
+
+ if (!doc || doc.location.href == s.iframeSrc) {
+ // response not received yet
+ if (!timedOut)
+ return;
+ }
+ io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
+
+ var status = 'success', errMsg;
+ try {
+ if (timedOut) {
+ throw 'timeout';
+ }
+
+ var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
+ log('isXml='+isXml);
+ if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
+ if (--domCheckCount) {
+ // in some browsers (Opera) the iframe DOM is not always traversable when
+ // the onload callback fires, so we loop a bit to accommodate
+ log('requeing onLoad callback, DOM not available');
+ setTimeout(cb, 250);
+ return;
+ }
+ // let this fall through because server response could be an empty document
+ //log('Could not access iframe DOM after mutiple tries.');
+ //throw 'DOMException: not available';
+ }
+
+ //log('response detected');
+ var docRoot = doc.body ? doc.body : doc.documentElement;
+ xhr.responseText = docRoot ? docRoot.innerHTML : null;
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+ if (isXml)
+ s.dataType = 'xml';
+ xhr.getResponseHeader = function(header){
+ var headers = {'content-type': s.dataType};
+ return headers[header];
+ };
+ // support for XHR 'status' & 'statusText' emulation :
+ if (docRoot) {
+ xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
+ xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
+ }
+
+ var dt = s.dataType || '';
+ var scr = /(json|script|text)/.test(dt.toLowerCase());
+ if (scr || s.textarea) {
+ // see if user embedded response in textarea
+ var ta = doc.getElementsByTagName('textarea')[0];
+ if (ta) {
+ xhr.responseText = ta.value;
+ // support for XHR 'status' & 'statusText' emulation :
+ xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
+ xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
+ }
+ else if (scr) {
+ // account for browsers injecting pre around json response
+ var pre = doc.getElementsByTagName('pre')[0];
+ var b = doc.getElementsByTagName('body')[0];
+ if (pre) {
+ xhr.responseText = pre.textContent ? pre.textContent : pre.innerHTML;
+ }
+ else if (b) {
+ xhr.responseText = b.innerHTML;
+ }
+ }
+ }
+ else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
+ xhr.responseXML = toXml(xhr.responseText);
+ }
+
+ try {
+ data = httpData(xhr, s.dataType, s);
+ }
+ catch (e) {
+ status = 'parsererror';
+ xhr.error = errMsg = (e || status);
+ }
+ }
+ catch (e) {
+ log('error caught: ',e);
+ status = 'error';
+ xhr.error = errMsg = (e || status);
+ }
+
+ if (xhr.aborted) {
+ log('upload aborted');
+ status = null;
+ }
+
+ if (xhr.status) { // we've set xhr.status
+ status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
+ }
+
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+ if (status === 'success') {
+ s.success && s.success.call(s.context, data, 'success', xhr);
+ g && $.event.trigger("ajaxSuccess", [xhr, s]);
+ }
+ else if (status) {
+ if (errMsg == undefined)
+ errMsg = xhr.statusText;
+ s.error && s.error.call(s.context, xhr, status, errMsg);
+ g && $.event.trigger("ajaxError", [xhr, s, errMsg]);
+ }
+
+ g && $.event.trigger("ajaxComplete", [xhr, s]);
+
+ if (g && ! --$.active) {
+ $.event.trigger("ajaxStop");
+ }
+
+ s.complete && s.complete.call(s.context, xhr, status);
+
+ callbackProcessed = true;
+ if (s.timeout)
+ clearTimeout(timeoutHandle);
+
+ // clean up
+ setTimeout(function() {
+ if (!s.iframeTarget)
+ $io.remove();
+ xhr.responseXML = null;
+ }, 100);
+ }
+
+ var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
+ if (window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML(s);
+ }
+ else {
+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
+ }
+ return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
+ };
+ var parseJSON = $.parseJSON || function(s) {
+ return window['eval']('(' + s + ')');
+ };
+
+ var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
+
+ var ct = xhr.getResponseHeader('content-type') || '',
+ xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if (xml && data.documentElement.nodeName === 'parsererror') {
+ $.error && $.error('parsererror');
+ }
+ if (s && s.dataFilter) {
+ data = s.dataFilter(data, type);
+ }
+ if (typeof data === 'string') {
+ if (type === 'json' || !type && ct.indexOf('json') >= 0) {
+ data = parseJSON(data);
+ } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
+ $.globalEval(data);
+ }
+ }
+ return data;
+ };
+ }
+};
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for elements (if the element
+ * is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ * used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */
+$.fn.ajaxForm = function(options) {
+ // in jQuery 1.3+ we can fix mistakes with the ready state
+ if (this.length === 0) {
+ var o = { s: this.selector, c: this.context };
+ if (!$.isReady && o.s) {
+ log('DOM not ready, queuing ajaxForm');
+ $(function() {
+ $(o.s,o.c).ajaxForm(options);
+ });
+ return this;
+ }
+ // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
+ log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
+ return this;
+ }
+
+ return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
+ if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
+ e.preventDefault();
+ $(this).ajaxSubmit(options);
+ }
+ }).bind('click.form-plugin', function(e) {
+ var target = e.target;
+ var $el = $(target);
+ if (!($el.is(":submit,input:image"))) {
+ // is this a child element of the submit el? (ex: a span within a button)
+ var t = $el.closest(':submit');
+ if (t.length == 0) {
+ return;
+ }
+ target = t[0];
+ }
+ var form = this;
+ form.clk = target;
+ if (target.type == 'image') {
+ if (e.offsetX != undefined) {
+ form.clk_x = e.offsetX;
+ form.clk_y = e.offsetY;
+ } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
+ var offset = $el.offset();
+ form.clk_x = e.pageX - offset.left;
+ form.clk_y = e.pageY - offset.top;
+ } else {
+ form.clk_x = e.pageX - target.offsetLeft;
+ form.clk_y = e.pageY - target.offsetTop;
+ }
+ }
+ // clear form vars
+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
+ });
+};
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+ return this.unbind('submit.form-plugin click.form-plugin');
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property. An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic) {
+ var a = [];
+ if (this.length === 0) {
+ return a;
+ }
+
+ var form = this[0];
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
+ if (!els) {
+ return a;
+ }
+
+ var i,j,n,v,el,max,jmax;
+ for(i=0, max=els.length; i < max; i++) {
+ el = els[i];
+ n = el.name;
+ if (!n) {
+ continue;
+ }
+
+ if (semantic && form.clk && el.type == "image") {
+ // handle image inputs on the fly when semantic == true
+ if(!el.disabled && form.clk == el) {
+ a.push({name: n, value: $(el).val()});
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ continue;
+ }
+
+ v = $.fieldValue(el, true);
+ if (v && v.constructor == Array) {
+ for(j=0, jmax=v.length; j < jmax; j++) {
+ a.push({name: n, value: v[j]});
+ }
+ }
+ else if (v !== null && typeof v != 'undefined') {
+ a.push({name: n, value: v});
+ }
+ }
+
+ if (!semantic && form.clk) {
+ // input type=='image' are not found in elements array! handle it here
+ var $input = $(form.clk), input = $input[0];
+ n = input.name;
+ if (n && !input.disabled && input.type == 'image') {
+ a.push({name: n, value: $input.val()});
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ }
+ return a;
+};
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+ //hand off to jQuery.param for proper encoding
+ return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+ var a = [];
+ this.each(function() {
+ var n = this.name;
+ if (!n) {
+ return;
+ }
+ var v = $.fieldValue(this, successful);
+ if (v && v.constructor == Array) {
+ for (var i=0,max=v.length; i < max; i++) {
+ a.push({name: n, value: v[i]});
+ }
+ }
+ else if (v !== null && typeof v != 'undefined') {
+ a.push({name: this.name, value: v});
+ }
+ });
+ //hand off to jQuery.param for proper encoding
+ return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
+ *
+ *
+ *
+ * var v = $(':text').fieldValue();
+ * // if no values are entered into the text inputs
+ * v == ['','']
+ * // if values entered into the text inputs are 'foo' and 'bar'
+ * v == ['foo','bar']
+ *
+ * var v = $(':checkbox').fieldValue();
+ * // if neither checkbox is checked
+ * v === undefined
+ * // if both checkboxes are checked
+ * v == ['B1', 'B2']
+ *
+ * var v = $(':radio').fieldValue();
+ * // if neither radio is checked
+ * v === undefined
+ * // if first radio is checked
+ * v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true. If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array. If no valid value can be determined the
+ * array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+ for (var val=[], i=0, max=this.length; i < max; i++) {
+ var el = this[i];
+ var v = $.fieldValue(el, successful);
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
+ continue;
+ }
+ v.constructor == Array ? $.merge(val, v) : val.push(v);
+ }
+ return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+ if (successful === undefined) {
+ successful = true;
+ }
+
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+ tag == 'select' && el.selectedIndex == -1)) {
+ return null;
+ }
+
+ if (tag == 'select') {
+ var index = el.selectedIndex;
+ if (index < 0) {
+ return null;
+ }
+ var a = [], ops = el.options;
+ var one = (t == 'select-one');
+ var max = (one ? index+1 : ops.length);
+ for(var i=(one ? index : 0); i < max; i++) {
+ var op = ops[i];
+ if (op.selected) {
+ var v = op.value;
+ if (!v) { // extra pain for IE...
+ v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
+ }
+ if (one) {
+ return v;
+ }
+ a.push(v);
+ }
+ }
+ return a;
+ }
+ return $(el).val();
+};
+
+/**
+ * Clears the form data. Takes the following actions on the form's input fields:
+ * - input text fields will have their 'value' property set to the empty string
+ * - select elements will have their 'selectedIndex' property set to -1
+ * - checkbox and radio inputs will have their 'checked' property set to false
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
+ * - button elements will *not* be effected
+ */
+$.fn.clearForm = function() {
+ return this.each(function() {
+ $('input,select,textarea', this).clearFields();
+ });
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function() {
+ var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
+ return this.each(function() {
+ var t = this.type, tag = this.tagName.toLowerCase();
+ if (re.test(t) || tag == 'textarea') {
+ this.value = '';
+ }
+ else if (t == 'checkbox' || t == 'radio') {
+ this.checked = false;
+ }
+ else if (tag == 'select') {
+ this.selectedIndex = -1;
+ }
+ });
+};
+
+/**
+ * Resets the form data. Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+ return this.each(function() {
+ // guard against an input with the name of 'reset'
+ // note that IE reports the reset function as an 'object'
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
+ this.reset();
+ }
+ });
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) {
+ if (b === undefined) {
+ b = true;
+ }
+ return this.each(function() {
+ this.disabled = !b;
+ });
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.selected = function(select) {
+ if (select === undefined) {
+ select = true;
+ }
+ return this.each(function() {
+ var t = this.type;
+ if (t == 'checkbox' || t == 'radio') {
+ this.checked = select;
+ }
+ else if (this.tagName.toLowerCase() == 'option') {
+ var $sel = $(this).parent('select');
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
+ // deselect all other options
+ $sel.find('option').selected(false);
+ }
+ this.selected = select;
+ }
+ });
+};
+
+// helper fn for console logging
+function log() {
+ var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
+ if (window.console && window.console.log) {
+ window.console.log(msg);
+ }
+ else if (window.opera && window.opera.postError) {
+ window.opera.postError(msg);
+ }
+};
+
+})(jQuery);
diff --git a/addons/base/static/lib/jquery.validate/jquery.validate.js b/addons/base/static/lib/jquery.validate/jquery.validate.js
new file mode 100644
index 00000000000..72296a61f20
--- /dev/null
+++ b/addons/base/static/lib/jquery.validate/jquery.validate.js
@@ -0,0 +1,1166 @@
+/**
+ * jQuery Validation Plugin 1.8.1
+ *
+ * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
+ * http://docs.jquery.com/Plugins/Validation
+ *
+ * Copyright (c) 2006 - 2011 Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ */
+
+(function($) {
+
+$.extend($.fn, {
+ // http://docs.jquery.com/Plugins/Validation/validate
+ validate: function( options ) {
+
+ // if nothing is selected, return nothing; can't chain anyway
+ if (!this.length) {
+ options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" );
+ return;
+ }
+
+ // check if a validator for this form was already created
+ var validator = $.data(this[0], 'validator');
+ if ( validator ) {
+ return validator;
+ }
+
+ validator = new $.validator( options, this[0] );
+ $.data(this[0], 'validator', validator);
+
+ if ( validator.settings.onsubmit ) {
+
+ // allow suppresing validation by adding a cancel class to the submit button
+ this.find("input, button").filter(".cancel").click(function() {
+ validator.cancelSubmit = true;
+ });
+
+ // when a submitHandler is used, capture the submitting button
+ if (validator.settings.submitHandler) {
+ this.find("input, button").filter(":submit").click(function() {
+ validator.submitButton = this;
+ });
+ }
+
+ // validate the form on submit
+ this.submit( function( event ) {
+ if ( validator.settings.debug )
+ // prevent form submit to be able to see console output
+ event.preventDefault();
+
+ function handle() {
+ if ( validator.settings.submitHandler ) {
+ if (validator.submitButton) {
+ // insert a hidden input as a replacement for the missing submit button
+ var hidden = $("").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm);
+ }
+ validator.settings.submitHandler.call( validator, validator.currentForm );
+ if (validator.submitButton) {
+ // and clean up afterwards; thanks to no-block-scope, hidden can be referenced
+ hidden.remove();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ // prevent submit for invalid forms or custom submit handlers
+ if ( validator.cancelSubmit ) {
+ validator.cancelSubmit = false;
+ return handle();
+ }
+ if ( validator.form() ) {
+ if ( validator.pendingRequest ) {
+ validator.formSubmitted = true;
+ return false;
+ }
+ return handle();
+ } else {
+ validator.focusInvalid();
+ return false;
+ }
+ });
+ }
+
+ return validator;
+ },
+ // http://docs.jquery.com/Plugins/Validation/valid
+ valid: function() {
+ if ( $(this[0]).is('form')) {
+ return this.validate().form();
+ } else {
+ var valid = true;
+ var validator = $(this[0].form).validate();
+ this.each(function() {
+ valid &= validator.element(this);
+ });
+ return valid;
+ }
+ },
+ // attributes: space seperated list of attributes to retrieve and remove
+ removeAttrs: function(attributes) {
+ var result = {},
+ $element = this;
+ $.each(attributes.split(/\s/), function(index, value) {
+ result[value] = $element.attr(value);
+ $element.removeAttr(value);
+ });
+ return result;
+ },
+ // http://docs.jquery.com/Plugins/Validation/rules
+ rules: function(command, argument) {
+ var element = this[0];
+
+ if (command) {
+ var settings = $.data(element.form, 'validator').settings;
+ var staticRules = settings.rules;
+ var existingRules = $.validator.staticRules(element);
+ switch(command) {
+ case "add":
+ $.extend(existingRules, $.validator.normalizeRule(argument));
+ staticRules[element.name] = existingRules;
+ if (argument.messages)
+ settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages );
+ break;
+ case "remove":
+ if (!argument) {
+ delete staticRules[element.name];
+ return existingRules;
+ }
+ var filtered = {};
+ $.each(argument.split(/\s/), function(index, method) {
+ filtered[method] = existingRules[method];
+ delete existingRules[method];
+ });
+ return filtered;
+ }
+ }
+
+ var data = $.validator.normalizeRules(
+ $.extend(
+ {},
+ $.validator.metadataRules(element),
+ $.validator.classRules(element),
+ $.validator.attributeRules(element),
+ $.validator.staticRules(element)
+ ), element);
+
+ // make sure required is at front
+ if (data.required) {
+ var param = data.required;
+ delete data.required;
+ data = $.extend({required: param}, data);
+ }
+
+ return data;
+ }
+});
+
+// Custom selectors
+$.extend($.expr[":"], {
+ // http://docs.jquery.com/Plugins/Validation/blank
+ blank: function(a) {return !$.trim("" + a.value);},
+ // http://docs.jquery.com/Plugins/Validation/filled
+ filled: function(a) {return !!$.trim("" + a.value);},
+ // http://docs.jquery.com/Plugins/Validation/unchecked
+ unchecked: function(a) {return !a.checked;}
+});
+
+// constructor for validator
+$.validator = function( options, form ) {
+ this.settings = $.extend( true, {}, $.validator.defaults, options );
+ this.currentForm = form;
+ this.init();
+};
+
+$.validator.format = function(source, params) {
+ if ( arguments.length == 1 )
+ return function() {
+ var args = $.makeArray(arguments);
+ args.unshift(source);
+ return $.validator.format.apply( this, args );
+ };
+ if ( arguments.length > 2 && params.constructor != Array ) {
+ params = $.makeArray(arguments).slice(1);
+ }
+ if ( params.constructor != Array ) {
+ params = [ params ];
+ }
+ $.each(params, function(i, n) {
+ source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
+ });
+ return source;
+};
+
+$.extend($.validator, {
+
+ defaults: {
+ messages: {},
+ groups: {},
+ rules: {},
+ errorClass: "error",
+ validClass: "valid",
+ errorElement: "label",
+ focusInvalid: true,
+ errorContainer: $( [] ),
+ errorLabelContainer: $( [] ),
+ onsubmit: true,
+ ignore: [],
+ ignoreTitle: false,
+ onfocusin: function(element) {
+ this.lastActive = element;
+
+ // hide error label and remove error class on focus if enabled
+ if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
+ this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
+ this.addWrapper(this.errorsFor(element)).hide();
+ }
+ },
+ onfocusout: function(element) {
+ if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) {
+ this.element(element);
+ }
+ },
+ onkeyup: function(element) {
+ if ( element.name in this.submitted || element == this.lastElement ) {
+ this.element(element);
+ }
+ },
+ onclick: function(element) {
+ // click on selects, radiobuttons and checkboxes
+ if ( element.name in this.submitted )
+ this.element(element);
+ // or option elements, check parent select in that case
+ else if (element.parentNode.name in this.submitted)
+ this.element(element.parentNode);
+ },
+ highlight: function(element, errorClass, validClass) {
+ if (element.type === 'radio') {
+ this.findByName(element.name).addClass(errorClass).removeClass(validClass);
+ } else {
+ $(element).addClass(errorClass).removeClass(validClass);
+ }
+ },
+ unhighlight: function(element, errorClass, validClass) {
+ if (element.type === 'radio') {
+ this.findByName(element.name).removeClass(errorClass).addClass(validClass);
+ } else {
+ $(element).removeClass(errorClass).addClass(validClass);
+ }
+ }
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults
+ setDefaults: function(settings) {
+ $.extend( $.validator.defaults, settings );
+ },
+
+ messages: {
+ required: "This field is required.",
+ remote: "Please fix this field.",
+ email: "Please enter a valid email address.",
+ url: "Please enter a valid URL.",
+ date: "Please enter a valid date.",
+ dateISO: "Please enter a valid date (ISO).",
+ number: "Please enter a valid number.",
+ digits: "Please enter only digits.",
+ creditcard: "Please enter a valid credit card number.",
+ equalTo: "Please enter the same value again.",
+ accept: "Please enter a value with a valid extension.",
+ maxlength: $.validator.format("Please enter no more than {0} characters."),
+ minlength: $.validator.format("Please enter at least {0} characters."),
+ rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
+ range: $.validator.format("Please enter a value between {0} and {1}."),
+ max: $.validator.format("Please enter a value less than or equal to {0}."),
+ min: $.validator.format("Please enter a value greater than or equal to {0}.")
+ },
+
+ autoCreateRanges: false,
+
+ prototype: {
+
+ init: function() {
+ this.labelContainer = $(this.settings.errorLabelContainer);
+ this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
+ this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer );
+ this.submitted = {};
+ this.valueCache = {};
+ this.pendingRequest = 0;
+ this.pending = {};
+ this.invalid = {};
+ this.reset();
+
+ var groups = (this.groups = {});
+ $.each(this.settings.groups, function(key, value) {
+ $.each(value.split(/\s/), function(index, name) {
+ groups[name] = key;
+ });
+ });
+ var rules = this.settings.rules;
+ $.each(rules, function(key, value) {
+ rules[key] = $.validator.normalizeRule(value);
+ });
+
+ function delegate(event) {
+ var validator = $.data(this[0].form, "validator"),
+ eventType = "on" + event.type.replace(/^validate/, "");
+ validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] );
+ }
+ $(this.currentForm)
+ .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate)
+ .validateDelegate(":radio, :checkbox, select, option", "click", delegate);
+
+ if (this.settings.invalidHandler)
+ $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/form
+ form: function() {
+ this.checkForm();
+ $.extend(this.submitted, this.errorMap);
+ this.invalid = $.extend({}, this.errorMap);
+ if (!this.valid())
+ $(this.currentForm).triggerHandler("invalid-form", [this]);
+ this.showErrors();
+ return this.valid();
+ },
+
+ checkForm: function() {
+ this.prepareForm();
+ for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
+ this.check( elements[i] );
+ }
+ return this.valid();
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/element
+ element: function( element ) {
+ element = this.clean( element );
+ this.lastElement = element;
+ this.prepareElement( element );
+ this.currentElements = $(element);
+ var result = this.check( element );
+ if ( result ) {
+ delete this.invalid[element.name];
+ } else {
+ this.invalid[element.name] = true;
+ }
+ if ( !this.numberOfInvalids() ) {
+ // Hide error containers on last error
+ this.toHide = this.toHide.add( this.containers );
+ }
+ this.showErrors();
+ return result;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/showErrors
+ showErrors: function(errors) {
+ if(errors) {
+ // add items to error list and map
+ $.extend( this.errorMap, errors );
+ this.errorList = [];
+ for ( var name in errors ) {
+ this.errorList.push({
+ message: errors[name],
+ element: this.findByName(name)[0]
+ });
+ }
+ // remove items from success list
+ this.successList = $.grep( this.successList, function(element) {
+ return !(element.name in errors);
+ });
+ }
+ this.settings.showErrors
+ ? this.settings.showErrors.call( this, this.errorMap, this.errorList )
+ : this.defaultShowErrors();
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/resetForm
+ resetForm: function() {
+ if ( $.fn.resetForm )
+ $( this.currentForm ).resetForm();
+ this.submitted = {};
+ this.prepareForm();
+ this.hideErrors();
+ this.elements().removeClass( this.settings.errorClass );
+ },
+
+ numberOfInvalids: function() {
+ return this.objectLength(this.invalid);
+ },
+
+ objectLength: function( obj ) {
+ var count = 0;
+ for ( var i in obj )
+ count++;
+ return count;
+ },
+
+ hideErrors: function() {
+ this.addWrapper( this.toHide ).hide();
+ },
+
+ valid: function() {
+ return this.size() == 0;
+ },
+
+ size: function() {
+ return this.errorList.length;
+ },
+
+ focusInvalid: function() {
+ if( this.settings.focusInvalid ) {
+ try {
+ $(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
+ .filter(":visible")
+ .focus()
+ // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
+ .trigger("focusin");
+ } catch(e) {
+ // ignore IE throwing errors when focusing hidden elements
+ }
+ }
+ },
+
+ findLastActive: function() {
+ var lastActive = this.lastActive;
+ return lastActive && $.grep(this.errorList, function(n) {
+ return n.element.name == lastActive.name;
+ }).length == 1 && lastActive;
+ },
+
+ elements: function() {
+ var validator = this,
+ rulesCache = {};
+
+ // select all valid inputs inside the form (no submit or reset buttons)
+ return $(this.currentForm)
+ .find("input, select, textarea")
+ .not(":submit, :reset, :image, [disabled]")
+ .not( this.settings.ignore )
+ .filter(function() {
+ !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this);
+
+ // select only the first element for each name, and only those with rules specified
+ if ( this.name in rulesCache || !validator.objectLength($(this).rules()) )
+ return false;
+
+ rulesCache[this.name] = true;
+ return true;
+ });
+ },
+
+ clean: function( selector ) {
+ return $( selector )[0];
+ },
+
+ errors: function() {
+ return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext );
+ },
+
+ reset: function() {
+ this.successList = [];
+ this.errorList = [];
+ this.errorMap = {};
+ this.toShow = $([]);
+ this.toHide = $([]);
+ this.currentElements = $([]);
+ },
+
+ prepareForm: function() {
+ this.reset();
+ this.toHide = this.errors().add( this.containers );
+ },
+
+ prepareElement: function( element ) {
+ this.reset();
+ this.toHide = this.errorsFor(element);
+ },
+
+ check: function( element ) {
+ element = this.clean( element );
+
+ // if radio/checkbox, validate first element in group instead
+ if (this.checkable(element)) {
+ element = this.findByName( element.name ).not(this.settings.ignore)[0];
+ }
+
+ var rules = $(element).rules();
+ var dependencyMismatch = false;
+ for (var method in rules ) {
+ var rule = { method: method, parameters: rules[method] };
+ try {
+ var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters );
+
+ // if a method indicates that the field is optional and therefore valid,
+ // don't mark it as valid when there are no other rules
+ if ( result == "dependency-mismatch" ) {
+ dependencyMismatch = true;
+ continue;
+ }
+ dependencyMismatch = false;
+
+ if ( result == "pending" ) {
+ this.toHide = this.toHide.not( this.errorsFor(element) );
+ return;
+ }
+
+ if( !result ) {
+ this.formatAndAdd( element, rule );
+ return false;
+ }
+ } catch(e) {
+ this.settings.debug && window.console && console.log("exception occured when checking element " + element.id
+ + ", check the '" + rule.method + "' method", e);
+ throw e;
+ }
+ }
+ if (dependencyMismatch)
+ return;
+ if ( this.objectLength(rules) )
+ this.successList.push(element);
+ return true;
+ },
+
+ // return the custom message for the given element and validation method
+ // specified in the element's "messages" metadata
+ customMetaMessage: function(element, method) {
+ if (!$.metadata)
+ return;
+
+ var meta = this.settings.meta
+ ? $(element).metadata()[this.settings.meta]
+ : $(element).metadata();
+
+ return meta && meta.messages && meta.messages[method];
+ },
+
+ // return the custom message for the given element name and validation method
+ customMessage: function( name, method ) {
+ var m = this.settings.messages[name];
+ return m && (m.constructor == String
+ ? m
+ : m[method]);
+ },
+
+ // return the first defined argument, allowing empty strings
+ findDefined: function() {
+ for(var i = 0; i < arguments.length; i++) {
+ if (arguments[i] !== undefined)
+ return arguments[i];
+ }
+ return undefined;
+ },
+
+ defaultMessage: function( element, method) {
+ return this.findDefined(
+ this.customMessage( element.name, method ),
+ this.customMetaMessage( element, method ),
+ // title is never undefined, so handle empty string as undefined
+ !this.settings.ignoreTitle && element.title || undefined,
+ $.validator.messages[method],
+ "Warning: No message defined for " + element.name + ""
+ );
+ },
+
+ formatAndAdd: function( element, rule ) {
+ var message = this.defaultMessage( element, rule.method ),
+ theregex = /\$?\{(\d+)\}/g;
+ if ( typeof message == "function" ) {
+ message = message.call(this, rule.parameters, element);
+ } else if (theregex.test(message)) {
+ message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters);
+ }
+ this.errorList.push({
+ message: message,
+ element: element
+ });
+
+ this.errorMap[element.name] = message;
+ this.submitted[element.name] = message;
+ },
+
+ addWrapper: function(toToggle) {
+ if ( this.settings.wrapper )
+ toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
+ return toToggle;
+ },
+
+ defaultShowErrors: function() {
+ for ( var i = 0; this.errorList[i]; i++ ) {
+ var error = this.errorList[i];
+ this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
+ this.showLabel( error.element, error.message );
+ }
+ if( this.errorList.length ) {
+ this.toShow = this.toShow.add( this.containers );
+ }
+ if (this.settings.success) {
+ for ( var i = 0; this.successList[i]; i++ ) {
+ this.showLabel( this.successList[i] );
+ }
+ }
+ if (this.settings.unhighlight) {
+ for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) {
+ this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
+ }
+ }
+ this.toHide = this.toHide.not( this.toShow );
+ this.hideErrors();
+ this.addWrapper( this.toShow ).show();
+ },
+
+ validElements: function() {
+ return this.currentElements.not(this.invalidElements());
+ },
+
+ invalidElements: function() {
+ return $(this.errorList).map(function() {
+ return this.element;
+ });
+ },
+
+ showLabel: function(element, message) {
+ var label = this.errorsFor( element );
+ if ( label.length ) {
+ // refresh error/success class
+ label.removeClass().addClass( this.settings.errorClass );
+
+ // check if we have a generated label, replace the message then
+ label.attr("generated") && label.html(message);
+ } else {
+ // create label
+ label = $("<" + this.settings.errorElement + "/>")
+ .attr({"for": this.idOrName(element), generated: true})
+ .addClass(this.settings.errorClass)
+ .html(message || "");
+ if ( this.settings.wrapper ) {
+ // make sure the element is visible, even in IE
+ // actually showing the wrapped element is handled elsewhere
+ label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
+ }
+ if ( !this.labelContainer.append(label).length )
+ this.settings.errorPlacement
+ ? this.settings.errorPlacement(label, $(element) )
+ : label.insertAfter(element);
+ }
+ if ( !message && this.settings.success ) {
+ label.text("");
+ typeof this.settings.success == "string"
+ ? label.addClass( this.settings.success )
+ : this.settings.success( label );
+ }
+ this.toShow = this.toShow.add(label);
+ },
+
+ errorsFor: function(element) {
+ var name = this.idOrName(element);
+ return this.errors().filter(function() {
+ return $(this).attr('for') == name;
+ });
+ },
+
+ idOrName: function(element) {
+ return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
+ },
+
+ checkable: function( element ) {
+ return /radio|checkbox/i.test(element.type);
+ },
+
+ findByName: function( name ) {
+ // select by name and filter by form for performance over form.find("[name=...]")
+ var form = this.currentForm;
+ return $(document.getElementsByName(name)).map(function(index, element) {
+ return element.form == form && element.name == name && element || null;
+ });
+ },
+
+ getLength: function(value, element) {
+ switch( element.nodeName.toLowerCase() ) {
+ case 'select':
+ return $("option:selected", element).length;
+ case 'input':
+ if( this.checkable( element) )
+ return this.findByName(element.name).filter(':checked').length;
+ }
+ return value.length;
+ },
+
+ depend: function(param, element) {
+ return this.dependTypes[typeof param]
+ ? this.dependTypes[typeof param](param, element)
+ : true;
+ },
+
+ dependTypes: {
+ "boolean": function(param, element) {
+ return param;
+ },
+ "string": function(param, element) {
+ return !!$(param, element.form).length;
+ },
+ "function": function(param, element) {
+ return param(element);
+ }
+ },
+
+ optional: function(element) {
+ return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch";
+ },
+
+ startRequest: function(element) {
+ if (!this.pending[element.name]) {
+ this.pendingRequest++;
+ this.pending[element.name] = true;
+ }
+ },
+
+ stopRequest: function(element, valid) {
+ this.pendingRequest--;
+ // sometimes synchronization fails, make sure pendingRequest is never < 0
+ if (this.pendingRequest < 0)
+ this.pendingRequest = 0;
+ delete this.pending[element.name];
+ if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) {
+ $(this.currentForm).submit();
+ this.formSubmitted = false;
+ } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) {
+ $(this.currentForm).triggerHandler("invalid-form", [this]);
+ this.formSubmitted = false;
+ }
+ },
+
+ previousValue: function(element) {
+ return $.data(element, "previousValue") || $.data(element, "previousValue", {
+ old: null,
+ valid: true,
+ message: this.defaultMessage( element, "remote" )
+ });
+ }
+
+ },
+
+ classRuleSettings: {
+ required: {required: true},
+ email: {email: true},
+ url: {url: true},
+ date: {date: true},
+ dateISO: {dateISO: true},
+ dateDE: {dateDE: true},
+ number: {number: true},
+ numberDE: {numberDE: true},
+ digits: {digits: true},
+ creditcard: {creditcard: true}
+ },
+
+ addClassRules: function(className, rules) {
+ className.constructor == String ?
+ this.classRuleSettings[className] = rules :
+ $.extend(this.classRuleSettings, className);
+ },
+
+ classRules: function(element) {
+ var rules = {};
+ var classes = $(element).attr('class');
+ classes && $.each(classes.split(' '), function() {
+ if (this in $.validator.classRuleSettings) {
+ $.extend(rules, $.validator.classRuleSettings[this]);
+ }
+ });
+ return rules;
+ },
+
+ attributeRules: function(element) {
+ var rules = {};
+ var $element = $(element);
+
+ for (var method in $.validator.methods) {
+ var value = $element.attr(method);
+ if (value) {
+ rules[method] = value;
+ }
+ }
+
+ // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs
+ if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) {
+ delete rules.maxlength;
+ }
+
+ return rules;
+ },
+
+ metadataRules: function(element) {
+ if (!$.metadata) return {};
+
+ var meta = $.data(element.form, 'validator').settings.meta;
+ return meta ?
+ $(element).metadata()[meta] :
+ $(element).metadata();
+ },
+
+ staticRules: function(element) {
+ var rules = {};
+ var validator = $.data(element.form, 'validator');
+ if (validator.settings.rules) {
+ rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
+ }
+ return rules;
+ },
+
+ normalizeRules: function(rules, element) {
+ // handle dependency check
+ $.each(rules, function(prop, val) {
+ // ignore rule when param is explicitly false, eg. required:false
+ if (val === false) {
+ delete rules[prop];
+ return;
+ }
+ if (val.param || val.depends) {
+ var keepRule = true;
+ switch (typeof val.depends) {
+ case "string":
+ keepRule = !!$(val.depends, element.form).length;
+ break;
+ case "function":
+ keepRule = val.depends.call(element, element);
+ break;
+ }
+ if (keepRule) {
+ rules[prop] = val.param !== undefined ? val.param : true;
+ } else {
+ delete rules[prop];
+ }
+ }
+ });
+
+ // evaluate parameters
+ $.each(rules, function(rule, parameter) {
+ rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter;
+ });
+
+ // clean number parameters
+ $.each(['minlength', 'maxlength', 'min', 'max'], function() {
+ if (rules[this]) {
+ rules[this] = Number(rules[this]);
+ }
+ });
+ $.each(['rangelength', 'range'], function() {
+ if (rules[this]) {
+ rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
+ }
+ });
+
+ if ($.validator.autoCreateRanges) {
+ // auto-create ranges
+ if (rules.min && rules.max) {
+ rules.range = [rules.min, rules.max];
+ delete rules.min;
+ delete rules.max;
+ }
+ if (rules.minlength && rules.maxlength) {
+ rules.rangelength = [rules.minlength, rules.maxlength];
+ delete rules.minlength;
+ delete rules.maxlength;
+ }
+ }
+
+ // To support custom messages in metadata ignore rule methods titled "messages"
+ if (rules.messages) {
+ delete rules.messages;
+ }
+
+ return rules;
+ },
+
+ // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
+ normalizeRule: function(data) {
+ if( typeof data == "string" ) {
+ var transformed = {};
+ $.each(data.split(/\s/), function() {
+ transformed[this] = true;
+ });
+ data = transformed;
+ }
+ return data;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/addMethod
+ addMethod: function(name, method, message) {
+ $.validator.methods[name] = method;
+ $.validator.messages[name] = message != undefined ? message : $.validator.messages[name];
+ if (method.length < 3) {
+ $.validator.addClassRules(name, $.validator.normalizeRule(name));
+ }
+ },
+
+ methods: {
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/required
+ required: function(value, element, param) {
+ // check if dependency is met
+ if ( !this.depend(param, element) )
+ return "dependency-mismatch";
+ switch( element.nodeName.toLowerCase() ) {
+ case 'select':
+ // could be an array for select-multiple or a string, both are fine this way
+ var val = $(element).val();
+ return val && val.length > 0;
+ case 'input':
+ if ( this.checkable(element) )
+ return this.getLength(value, element) > 0;
+ default:
+ return $.trim(value).length > 0;
+ }
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/remote
+ remote: function(value, element, param) {
+ if ( this.optional(element) )
+ return "dependency-mismatch";
+
+ var previous = this.previousValue(element);
+ if (!this.settings.messages[element.name] )
+ this.settings.messages[element.name] = {};
+ previous.originalMessage = this.settings.messages[element.name].remote;
+ this.settings.messages[element.name].remote = previous.message;
+
+ param = typeof param == "string" && {url:param} || param;
+
+ if ( this.pending[element.name] ) {
+ return "pending";
+ }
+ if ( previous.old === value ) {
+ return previous.valid;
+ }
+
+ previous.old = value;
+ var validator = this;
+ this.startRequest(element);
+ var data = {};
+ data[element.name] = value;
+ $.ajax($.extend(true, {
+ url: param,
+ mode: "abort",
+ port: "validate" + element.name,
+ dataType: "json",
+ data: data,
+ success: function(response) {
+ validator.settings.messages[element.name].remote = previous.originalMessage;
+ var valid = response === true;
+ if ( valid ) {
+ var submitted = validator.formSubmitted;
+ validator.prepareElement(element);
+ validator.formSubmitted = submitted;
+ validator.successList.push(element);
+ validator.showErrors();
+ } else {
+ var errors = {};
+ var message = response || validator.defaultMessage( element, "remote" );
+ errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
+ validator.showErrors(errors);
+ }
+ previous.valid = valid;
+ validator.stopRequest(element, valid);
+ }
+ }, param));
+ return "pending";
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/minlength
+ minlength: function(value, element, param) {
+ return this.optional(element) || this.getLength($.trim(value), element) >= param;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/maxlength
+ maxlength: function(value, element, param) {
+ return this.optional(element) || this.getLength($.trim(value), element) <= param;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/rangelength
+ rangelength: function(value, element, param) {
+ var length = this.getLength($.trim(value), element);
+ return this.optional(element) || ( length >= param[0] && length <= param[1] );
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/min
+ min: function( value, element, param ) {
+ return this.optional(element) || value >= param;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/max
+ max: function( value, element, param ) {
+ return this.optional(element) || value <= param;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/range
+ range: function( value, element, param ) {
+ return this.optional(element) || ( value >= param[0] && value <= param[1] );
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/email
+ email: function(value, element) {
+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
+ return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/url
+ url: function(value, element) {
+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
+ return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/date
+ date: function(value, element) {
+ return this.optional(element) || !/Invalid|NaN/.test(new Date(value));
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/dateISO
+ dateISO: function(value, element) {
+ return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/number
+ number: function(value, element) {
+ return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/digits
+ digits: function(value, element) {
+ return this.optional(element) || /^\d+$/.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/creditcard
+ // based on http://en.wikipedia.org/wiki/Luhn
+ creditcard: function(value, element) {
+ if ( this.optional(element) )
+ return "dependency-mismatch";
+ // accept only digits and dashes
+ if (/[^0-9-]+/.test(value))
+ return false;
+ var nCheck = 0,
+ nDigit = 0,
+ bEven = false;
+
+ value = value.replace(/\D/g, "");
+
+ for (var n = value.length - 1; n >= 0; n--) {
+ var cDigit = value.charAt(n);
+ var nDigit = parseInt(cDigit, 10);
+ if (bEven) {
+ if ((nDigit *= 2) > 9)
+ nDigit -= 9;
+ }
+ nCheck += nDigit;
+ bEven = !bEven;
+ }
+
+ return (nCheck % 10) == 0;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/accept
+ accept: function(value, element, param) {
+ param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif";
+ return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i"));
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/equalTo
+ equalTo: function(value, element, param) {
+ // bind to the blur event of the target in order to revalidate whenever the target field is updated
+ // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
+ var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
+ $(element).valid();
+ });
+ return value == target.val();
+ }
+
+ }
+
+});
+
+// deprecated, use $.validator.format instead
+$.format = $.validator.format;
+
+})(jQuery);
+
+// ajax mode: abort
+// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
+// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
+;(function($) {
+ var pendingRequests = {};
+ // Use a prefilter if available (1.5+)
+ if ( $.ajaxPrefilter ) {
+ $.ajaxPrefilter(function(settings, _, xhr) {
+ var port = settings.port;
+ if (settings.mode == "abort") {
+ if ( pendingRequests[port] ) {
+ pendingRequests[port].abort();
+ }
+ pendingRequests[port] = xhr;
+ }
+ });
+ } else {
+ // Proxy ajax
+ var ajax = $.ajax;
+ $.ajax = function(settings) {
+ var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode,
+ port = ( "port" in settings ? settings : $.ajaxSettings ).port;
+ if (mode == "abort") {
+ if ( pendingRequests[port] ) {
+ pendingRequests[port].abort();
+ }
+ return (pendingRequests[port] = ajax.apply(this, arguments));
+ }
+ return ajax.apply(this, arguments);
+ };
+ }
+})(jQuery);
+
+// provides cross-browser focusin and focusout events
+// IE has native support, in other browsers, use event caputuring (neither bubbles)
+
+// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
+// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
+;(function($) {
+ // only implement if not provided by jQuery core (since 1.4)
+ // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs
+ if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) {
+ $.each({
+ focus: 'focusin',
+ blur: 'focusout'
+ }, function( original, fix ){
+ $.event.special[fix] = {
+ setup:function() {
+ this.addEventListener( original, handler, true );
+ },
+ teardown:function() {
+ this.removeEventListener( original, handler, true );
+ },
+ handler: function(e) {
+ arguments[0] = $.event.fix(e);
+ arguments[0].type = fix;
+ return $.event.handle.apply(this, arguments);
+ }
+ };
+ function handler(e) {
+ e = $.event.fix(e);
+ e.type = fix;
+ return $.event.handle.call(this, e);
+ }
+ });
+ };
+ $.extend($.fn, {
+ validateDelegate: function(delegate, type, handler) {
+ return this.bind(type, function(event) {
+ var target = $(event.target);
+ if (target.is(delegate)) {
+ return handler.apply(target, arguments);
+ }
+ });
+ }
+ });
+})(jQuery);
diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css
index d19140a8a49..274553c1b5e 100644
--- a/addons/base/static/src/css/base.css
+++ b/addons/base/static/src/css/base.css
@@ -30,9 +30,13 @@ body.openerp {
.openerp .oe-number {
text-align: right !important;
}
+.openerp .oe_hide {
+ display: none !important;
+}
/* STATES */
-.openerp .on_logged {
+.openerp .on_logged,
+.openerp .db_options_row {
display: none;
}
@@ -135,10 +139,84 @@ body.openerp {
}
.openerp.login-mode .menu,
.openerp.login-mode .secondary_menu,
-.openerp.login-mode .oe-application {
+.openerp.login-mode .oe-application,
+.openerp.login-mode .db_options_row {
display: none;
}
+/* Database */
+.openerp.database_block .db_options_row {
+ height: 100%;
+ display: table-row;
+}
+
+.openerp.database_block .menu,
+.openerp.database_block .secondary_menu,
+.openerp.database_block .oe-application,
+.openerp.database_block .login-container {
+ display: none;
+}
+
+.db_container {
+ width: 15%;
+ background: #666666;
+}
+
+ul.db_options li {
+ padding: 5px 0 10px 5px;
+ background: #949292; /* Old browsers */
+ background: -moz-linear-gradient(top, #949292 30%, #6d6b6b 95%, #282828 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(30%,#949292), color-stop(95%,#6d6b6b), color-stop(100%,#282828)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #949292 30%,#6d6b6b 95%,#282828 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #949292 30%,#6d6b6b 95%,#282828 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #949292 30%,#6d6b6b 95%,#282828 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#282828',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #949292 30%,#6d6b6b 95%,#282828 100%); /* W3C */
+ /* for ie9 */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#5B5A5A',GradientType=0 ); /* IE6-9 */
+ border: none;
+ /* overriding jquery ui */
+ -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px;
+ display: block;
+ font-weight: bold;
+ text-transform: uppercase;
+ margin: 1px;
+ color: #EEEEEE;
+ cursor: pointer;
+ width: 196px;
+ font-size: 12px;
+}
+
+.db_option_table {
+ border: 1px solid #5A5858;
+ padding: 5px;
+ -moz-border-radius: 10px;
+}
+
+table.db_option_table input.required {
+ background-color: #D2D2FF;
+}
+
+.db_option_table input[type="text"], .db_option_table input[type="password"], .db_option_table select {
+ width: 300px;
+}
+
+.option_string {
+ font-weight: bold;
+ color: #555;
+ width: 100%;
+ text-align: center;
+ padding: 10px 0;
+ font-size: large;
+}
+
+label.error {
+ float: none;
+ color: red;
+ padding-left: .5em;
+ vertical-align: top;
+}
+
/* Main*/
.openerp .main_table {
width: 100%;
@@ -158,14 +236,14 @@ body.openerp {
.openerp .menu {
height: 34px;
-background: #cc4e45; /* Old browsers */
-background: -moz-linear-gradient(top, #cc4e45 0%, #b52d20 8%, #7a211a 100%); /* FF3.6+ */
-background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#cc4e45), color-stop(8%,#b52d20), color-stop(100%,#7a211a)); /* Chrome,Safari4+ */
-background: -webkit-linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* Chrome10+,Safari5.1+ */
-background: -o-linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* Opera11.10+ */
-background: -ms-linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* IE10+ */
-filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#CC4E45', endColorstr='#7A211A',GradientType=0 ); /* IE6-9 */
-background: linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* W3C */
+ background: #cc4e45; /* Old browsers */
+ background: -moz-linear-gradient(top, #cc4e45 0%, #b52d20 8%, #7a211a 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#cc4e45), color-stop(8%,#b52d20), color-stop(100%,#7a211a)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#CC4E45', endColorstr='#7A211A',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* W3C */
}
.openerp .menu td {
text-align: center;
@@ -178,14 +256,14 @@ background: linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* W3C */
margin: 3px 2px;
padding: 0 8px;
-background: #bd5e54; /* Old browsers */
-background: -moz-linear-gradient(top, #bd5e54 0%, #90322a 60%); /* FF3.6+ */
-background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bd5e54), color-stop(60%,#90322a)); /* Chrome,Safari4+ */
-background: -webkit-linear-gradient(top, #bd5e54 0%,#90322a 60%); /* Chrome10+,Safari5.1+ */
-background: -o-linear-gradient(top, #bd5e54 0%,#90322a 60%); /* Opera11.10+ */
-background: -ms-linear-gradient(top, #bd5e54 0%,#90322a 60%); /* IE10+ */
-filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#BD5E54', endColorstr='#90322A',GradientType=0 ); /* IE6-9 */
-background: linear-gradient(top, #bd5e54 0%,#90322a 60%); /* W3C */
+ background: #bd5e54; /* Old browsers */
+ background: -moz-linear-gradient(top, #bd5e54 0%, #90322a 60%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bd5e54), color-stop(60%,#90322a)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #bd5e54 0%,#90322a 60%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #bd5e54 0%,#90322a 60%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #bd5e54 0%,#90322a 60%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#BD5E54', endColorstr='#90322A',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #bd5e54 0%,#90322a 60%); /* W3C */
border: 1px solid #6E2A24;
border-radius: 4px;
@@ -205,16 +283,16 @@ background: linear-gradient(top, #bd5e54 0%,#90322a 60%); /* W3C */
.openerp .menu a:hover,
.openerp .menu a:focus,
.openerp .menu a.active {
-background: #c6c6c6; /* Old browsers */
-background: -moz-linear-gradient(top, #c6c6c6 0%, #5c5c5c 7%, #969595 86%); /* FF3.6+ */
-background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#c6c6c6), color-stop(7%,#5c5c5c), color-stop(86%,#969595)); /* Chrome,Safari4+ */
-background: -webkit-linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* Chrome10+,Safari5.1+ */
-background: -o-linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* Opera11.10+ */
-background: -ms-linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* IE10+ */
-filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#C6C6C6', endColorstr='#969595',GradientType=0 ); /* IE6-9 */
-background: linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* W3C */
-/* for ie */
-filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#5c5c5c', endColorstr='#969595',GradientType=0 ); /* IE6-9 */
+ background: #c6c6c6; /* Old browsers */
+ background: -moz-linear-gradient(top, #c6c6c6 0%, #5c5c5c 7%, #969595 86%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#c6c6c6), color-stop(7%,#5c5c5c), color-stop(86%,#969595)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#C6C6C6', endColorstr='#969595',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* W3C */
+ /* for ie */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#5c5c5c', endColorstr='#969595',GradientType=0 ); /* IE6-9 */
color: #fff;
}
@@ -236,19 +314,19 @@ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#5c5c5c', end
}
.openerp .secondary_menu h3 {
padding: 0 0 2px;
-background: #949292; /* Old browsers */
-background: -moz-linear-gradient(top, #949292 0%, #6d6b6b 87%, #282828 99%); /* FF3.6+ */
-background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#949292), color-stop(87%,#6d6b6b), color-stop(99%,#282828)); /* Chrome,Safari4+ */
-background: -webkit-linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* Chrome10+,Safari5.1+ */
-background: -o-linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* Opera11.10+ */
-background: -ms-linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* IE10+ */
-filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#282828',GradientType=0 ); /* IE6-9 */
-background: linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* W3C */
-/* for ie9 */
-filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#5B5A5A',GradientType=0 ); /* IE6-9 */
+ background: #949292; /* Old browsers */
+ background: -moz-linear-gradient(top, #949292 0%, #6d6b6b 87%, #282828 99%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#949292), color-stop(87%,#6d6b6b), color-stop(99%,#282828)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#282828',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* W3C */
+ /* for ie9 */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#5B5A5A',GradientType=0 ); /* IE6-9 */
border: none;
/* overriding jquery ui */
--moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px;
+ -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px;
}
.openerp .secondary_menu h4 {
padding: 0 0 2px 10px;
@@ -274,17 +352,16 @@ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', end
.openerp .secondary_menu h4:hover,
.openerp .secondary_menu h4:active,
.openerp .secondary_menu h4.active {
-background: #ffffff; /* Old browsers */
-background: -moz-linear-gradient(top, #ffffff 0%, #d8d8d8 11%, #afafaf 86%, #333333 91%, #5a5858 96%); /* FF3.6+ */
-background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(11%,#d8d8d8), color-stop(86%,#afafaf), color-stop(91%,#333333), color-stop(96%,#5a5858)); /* Chrome,Safari4+ */
-background: -webkit-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* Chrome10+,Safari5.1+ */
-background: -o-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* Opera11.10+ */
-background: -ms-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* IE10+ */
-filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#5A5858',GradientType=0 ); /* IE6-9 */
-background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* W3C */
+ background: #ffffff; /* Old browsers */
+ background: -moz-linear-gradient(top, #ffffff 0%, #d8d8d8 11%, #afafaf 86%, #333333 91%, #5a5858 96%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(11%,#d8d8d8), color-stop(86%,#afafaf), color-stop(91%,#333333), color-stop(96%,#5a5858)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#5A5858',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* W3C */
/* overriding jquery ui */
--moz-border-radius-topright: 0; -webkit-border-top-right-radius: 0; border-top-right-radius: 0;
-
+ -moz-border-radius-topright: 0; -webkit-border-top-right-radius: 0; border-top-right-radius: 0;
color: #3f3d3d;
text-shadow: #fff 0 1px 0;
border: none !important;
@@ -314,7 +391,6 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
float: left;
height: 63px;
width: 200px;
- margin-right: 10px;
border: 1px solid white;
border-right-color: black;
border-bottom-color: black;
@@ -778,7 +854,7 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
}
/* Inputs */
-.openerp input[type="text"], .openerp input[type="password"], .openerp select, .openerp textarea {
+.openerp .oe_forms input[type="text"], .openerp .oe_forms input[type="password"], .openerp .oe_forms select, .openerp .oe_forms textarea {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
@@ -792,17 +868,17 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
min-width: 90px;
color: #1f1f1f;
}
-.openerp textarea {
+.openerp .oe_forms textarea {
resize:vertical;
}
-.openerp input[type="text"], .openerp input[type="password"], .openerp select, .openerp .button {
+.openerp .oe_forms input[type="text"], .openerp .oe_forms input[type="password"], .openerp .oe_forms select, .openerp .oe_forms .button {
height: 22px;
}
-.openerp .button {
+.openerp .oe_forms .button {
color: #4c4c4c;
white-space: nowrap;
}
-.openerp .button span {
+.openerp .oe_forms .button span {
position: relative;
vertical-align: top;
}
@@ -1021,6 +1097,10 @@ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */
.openerp .closed-sidebar .toggle-sidebar {
border-left: none;
}
+.openerp li.oe_sidebar_print {
+ padding-left: 20px;
+ background: 1px 3px url(../img/icons/gtk-print.png) no-repeat;
+}
.openerp.kitten-mode-activated .main_table {
background: url(http://placekitten.com/g/1500/800) repeat;
@@ -1093,3 +1173,10 @@ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */
font-size: 1.2em;
font-weight: bold;
}
+
+.openerp .dhx_mini_calendar {
+ -moz-box-shadow: none;
+ -khtml-box-shadow: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
diff --git a/addons/base/static/src/css/data_export.css b/addons/base/static/src/css/data_export.css
new file mode 100644
index 00000000000..7b4b9707b39
--- /dev/null
+++ b/addons/base/static/src/css/data_export.css
@@ -0,0 +1,85 @@
+.openerp .oe_export_row tr{
+ background-color: #FFFFFF;
+ font-size: 0.8em;
+ height: 22px;
+}
+
+.openerp tr.ui-selected td {
+ background-color: #CCCCCC;
+}
+
+.openerp .oe_export_requiredfield {
+ background-color: #D2D2FF;
+}
+
+.openerp .oe_export_readonlyfield{
+ background-color: #999999;
+}
+
+.openerp .oe_export_row:hover{
+ background-color: #F3F3F3;
+}
+
+.openerp .oe_export_fields_selector_export {
+ width: 100%;
+ height: 400px;
+}
+
+.openerp .oe_export_fields_selector_left {
+ width: 50%;
+}
+
+.openerp div#left_field_panel {
+ overflow: scroll;
+ width: 100%;
+ height: 400px;
+ border: solid #999999 1px;
+}
+
+.openerp .oe_export_fields_selector_center {
+ width: 102px;
+}
+
+.openerp .oe_export_fields_selector_right {
+ width: 45%;
+ height: 400px;
+}
+
+.openerp .oe_export_fields_selector_export select{
+ width: 100%;
+ height: 100%;
+}
+
+.openerp .oe_export_tree_header{
+ border: 0.5px solid #E3E3E3;
+ text-align: left;
+ white-space: nowrap;
+ padding: 4px 5px;
+ background: url(/base/static/src/img/header.gif);
+}
+
+
+.openerp table.tree_grid{
+ border: 1px solid #E3E3E3;
+ text-align: left;
+ white-space: nowrap;
+ background-color:#E3E3E3;
+ border-collapse: collapse;
+ width: 100%;
+}
+
+.openerp table.tree_grid a:hover {
+ color: blue;
+ border: none;
+}
+
+.openerp table.tree_grid a {
+ color: #5F5C5C;
+ border: none;
+ display: block;
+}
+
+.openerp .oe_export_button_export {
+ border: 1px solid #006;
+ background-color: #F3F3F3;
+}
diff --git a/addons/base/static/src/img/collapse.gif b/addons/base/static/src/img/collapse.gif
new file mode 100644
index 00000000000..759e72ebcfe
Binary files /dev/null and b/addons/base/static/src/img/collapse.gif differ
diff --git a/addons/base/static/src/img/db.png b/addons/base/static/src/img/db.png
new file mode 100644
index 00000000000..83e42df26b3
Binary files /dev/null and b/addons/base/static/src/img/db.png differ
diff --git a/addons/base/static/src/img/expand.gif b/addons/base/static/src/img/expand.gif
new file mode 100644
index 00000000000..ca98d0b5f2b
Binary files /dev/null and b/addons/base/static/src/img/expand.gif differ
diff --git a/addons/base/static/src/img/header.gif b/addons/base/static/src/img/header.gif
new file mode 100644
index 00000000000..f40bd9c0f6a
Binary files /dev/null and b/addons/base/static/src/img/header.gif differ
diff --git a/addons/base/static/src/js/base.js b/addons/base/static/src/js/boot.js
similarity index 95%
rename from addons/base/static/src/js/base.js
rename to addons/base/static/src/js/boot.js
index 28022f8b764..c16f2d261f5 100644
--- a/addons/base/static/src/js/base.js
+++ b/addons/base/static/src/js/boot.js
@@ -1,5 +1,5 @@
//---------------------------------------------------------
-// OpenERP initialisation and black magic about the pool
+// OpenERP Web Boostrap
//---------------------------------------------------------
/**
@@ -57,9 +57,8 @@
// OpenERP base module split
//---------------------------------------------------------
-/** @namespace */
openerp.base = function(instance) {
- openerp.base.controller(instance);
+ openerp.base.core(instance);
openerp.base.dates(instance);
openerp.base.chrome(instance);
openerp.base.data(instance);
@@ -87,6 +86,9 @@ openerp.base = function(instance) {
if (openerp.base.view_tree) {
openerp.base.view_tree(instance);
}
+ if (openerp.base.data_export) {
+ openerp.base.data_export(instance);
+ }
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js
index a435850ce20..fd5fae7727d 100644
--- a/addons/base/static/src/js/chrome.js
+++ b/addons/base/static/src/js/chrome.js
@@ -4,241 +4,7 @@
openerp.base.chrome = function(openerp) {
-/**
- * Base error for lookup failure
- *
- * @class
- */
-openerp.base.NotFound = openerp.base.Class.extend( /** @lends openerp.base.NotFound# */ {
-});
-openerp.base.KeyNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.KeyNotFound# */ {
- /**
- * Thrown when a key could not be found in a mapping
- *
- * @constructs
- * @extends openerp.base.NotFound
- * @param {String} key the key which could not be found
- */
- init: function (key) {
- this.key = key;
- },
- toString: function () {
- return "The key " + this.key + " was not found";
- }
-});
-openerp.base.ObjectNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.ObjectNotFound# */ {
- /**
- * Thrown when an object path does not designate a valid class or object
- * in the openerp hierarchy.
- *
- * @constructs
- * @extends openerp.base.NotFound
- * @param {String} path the invalid object path
- */
- init: function (path) {
- this.path = path;
- },
- toString: function () {
- return "Could not find any object of path " + this.path;
- }
-});
-openerp.base.Registry = openerp.base.Class.extend( /** @lends openerp.base.Registry# */ {
- /**
- * Stores a mapping of arbitrary key (strings) to object paths (as strings
- * as well).
- *
- * Resolves those paths at query time in order to always fetch the correct
- * object, even if those objects have been overloaded/replaced after the
- * registry was created.
- *
- * An object path is simply a dotted name from the openerp root to the
- * object pointed to (e.g. ``"openerp.base.Session"`` for an OpenERP
- * session object).
- *
- * @constructs
- * @param {Object} mapping a mapping of keys to object-paths
- */
- init: function (mapping) {
- this.map = mapping || {};
- },
- /**
- * Retrieves the object matching the provided key string.
- *
- * @param {String} key the key to fetch the object for
- * @returns {Class} the stored class, to initialize
- *
- * @throws {openerp.base.KeyNotFound} if the object was not in the mapping
- * @throws {openerp.base.ObjectNotFound} if the object path was invalid
- */
- get_object: function (key) {
- var path_string = this.map[key];
- if (path_string === undefined) {
- throw new openerp.base.KeyNotFound(key);
- }
-
- var object_match = openerp;
- var path = path_string.split('.');
- // ignore first section
- for(var i=1; i').dialog({
+ modal: true,
+ title: error.title,
+ buttons: {
+ Ok: function() {
+ $(this).dialog("close");
+ }
+ }
+ }).html(error.error);
+ },
+ do_create: function() {
+ var self = this;
+ self.$option_id.html(QWeb.render("CreateDB", self));
+
+ self.$option_id.find("form[name=create_db_form]").validate({
+ submitHandler: function (form) {
+ var fields = $(form).serializeArray();
+ $.blockUI();
+ self.rpc("/base/database/create", {'fields': fields}, function(result) {
+ if (result.error) {
+ $.unblockUI();
+ self.display_error(result);
+ return;
+ }
+ self.db_list.push(self.to_object(fields)['db_name']);
+ self.db_list.sort();
+ var form_obj = self.to_object(fields);
+ self.wait_for_newdb(result, {
+ password: form_obj['super_admin_pwd'],
+ db: form_obj['db_name']
+ });
+ });
+ }
+ });
+ },
+
+ do_drop: function() {
+ var self = this;
+ self.$option_id.html(QWeb.render("DropDB", self));
+
+ self.$option_id.find("form[name=drop_db_form]").validate({
+ submitHandler: function (form) {
+ var $form = $(form),
+ fields = $form.serializeArray(),
+ $db_list = $form.find('select[name=drop_db]'),
+ db = $db_list.val();
+
+ if (!confirm("Do you really want to delete the database: " + db + " ?")) {
+ return;
+ }
+ self.rpc("/base/database/drop", {'fields': fields}, function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
+ }
+ $db_list.find(':selected').remove();
+ self.db_list.splice(_.indexOf(self.db_list, db, true), 1);
+ self.notification.notify("Dropping database", "The database '" + db + "' has been dropped");
+ });
+ }
+ });
+ },
+
+ wait_for_file: function (token, cleanup) {
+ var self = this,
+ cookie_name = 'fileToken',
+ cookie_length = cookie_name.length;
+ this.backup_timer = setInterval(function () {
+ var cookies = document.cookie.split(';');
+ for(var i=0; i')
+ .appendTo(document.body)
+ .load(function () {
+ $.unblockUI();
+ clearInterval(self.backup_timer);
+ var error = this.contentDocument.body
+ .firstChild.data
+ .split('|');
+ self.display_error({
+ title: error[0],
+ error: error[1]
+ });
+ });
+ }
+ $(form).find('input[name=token]').val(token);
+ form.submit();
+
+ self.wait_for_file(token, function () {
+ $.unblockUI();
+ });
+ }
+ });
+ },
+
+ do_restore: function() {
+ var self = this;
+ self.$option_id.html(QWeb.render("RestoreDB", self));
+
+ self.$option_id.find("form[name=restore_db_form]").validate({
+ submitHandler: function (form) {
+ $.blockUI();
+ $(form).ajaxSubmit({
+ url: '/base/database/restore',
+ type: 'POST',
+ resetForm: true,
+ success: function (body) {
+ // TODO: ui manipulations
+ // note: response objects don't work, but we have the
+ // HTTP body of the response~~
+
+ // If empty body, everything went fine
+ if (!body) { return; }
+
+ if (body.indexOf('403 Forbidden') !== -1) {
+ self.display_error({
+ title: 'Access Denied',
+ error: 'Incorrect super-administrator password'
+ })
+ } else {
+ self.display_error({
+ title: 'Restore Database',
+ error: 'Could not restore the database'
+ })
+ }
+ },
+ complete: function () {
+ $.unblockUI();
+ }
+ });
+ }
+ });
+ },
+
+ do_change_password: function() {
+ var self = this;
+ self.$option_id.html(QWeb.render("Change_DB_Pwd", self));
+
+ self.$option_id.find("form[name=change_pwd_form]").validate({
+ messages: {
+ old_pwd: "Please enter your previous password",
+ new_pwd: "Please enter your new password",
+ confirm_pwd: {
+ required: "Please confirm your new password",
+ equalTo: "The confirmation does not match the password"
+ }
+ },
+ submitHandler: function (form) {
+ self.rpc("/base/database/change_password", {
+ 'fields': $(form).serializeArray()
+ }, function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
+ }
+ self.notification.notify("Changed Password", "Password has been changed successfully");
+ });
+ }
+ });
+ }
});
-openerp.base.Login = openerp.base.Controller.extend({
+openerp.base.Login = openerp.base.Widget.extend({
remember_creditentials: true,
+
init: function(parent, element_id) {
this._super(parent, element_id);
this.has_local_storage = typeof(localStorage) != 'undefined';
this.selected_db = null;
this.selected_login = null;
+
if (this.has_local_storage && this.remember_creditentials) {
this.selected_db = localStorage.getItem('last_db_login_success');
this.selected_login = localStorage.getItem('last_login_login_success');
@@ -699,7 +771,7 @@ openerp.base.Login = openerp.base.Controller.extend({
},
start: function() {
var self = this;
- this.rpc("/base/database/get_databases_list", {}, function(result) {
+ this.rpc("/base/database/get_list", {}, function(result) {
self.db_list = result.db_list;
self.display();
}, function() {
@@ -707,7 +779,16 @@ openerp.base.Login = openerp.base.Controller.extend({
});
},
display: function() {
+ var self = this;
+
this.$element.html(QWeb.render("Login", this));
+ this.database = new openerp.base.Database(
+ this, "oe_database", "oe_db_options");
+
+ this.$element.find('#oe-db-config').click(function() {
+ self.database.start();
+ });
+
this.$element.find("form").submit(this.on_submit);
},
on_login_invalid: function() {
@@ -718,13 +799,22 @@ openerp.base.Login = openerp.base.Controller.extend({
},
on_submit: function(ev) {
ev.preventDefault();
- var self = this;
var $e = this.$element;
var db = $e.find("form [name=db]").val();
var login = $e.find("form input[name=login]").val();
var password = $e.find("form input[name=password]").val();
- //$e.hide();
- // Should hide then call callback
+
+ this.do_login(db, login, password);
+ },
+ /**
+ * Performs actual login operation, and UI-related stuff
+ *
+ * @param {String} db database to log in
+ * @param {String} login user login
+ * @param {String} password user password
+ */
+ do_login: function (db, login, password) {
+ var self = this;
this.session.session_login(db, login, password, function() {
if(self.session.session_is_valid()) {
if (self.has_local_storage) {
@@ -758,7 +848,7 @@ openerp.base.Login = openerp.base.Controller.extend({
}
});
-openerp.base.Header = openerp.base.Controller.extend({
+openerp.base.Header = openerp.base.Widget.extend({
init: function(parent, element_id) {
this._super(parent, element_id);
},
@@ -772,7 +862,7 @@ openerp.base.Header = openerp.base.Controller.extend({
on_logout: function() {}
});
-openerp.base.Menu = openerp.base.Controller.extend({
+openerp.base.Menu = openerp.base.Widget.extend({
init: function(parent, element_id, secondary_menu_id) {
this._super(parent, element_id);
this.secondary_menu_id = secondary_menu_id;
@@ -853,16 +943,16 @@ openerp.base.Menu = openerp.base.Controller.extend({
}
});
-openerp.base.Homepage = openerp.base.Controller.extend({
+openerp.base.Homepage = openerp.base.Widget.extend({
});
-openerp.base.Preferences = openerp.base.Controller.extend({
+openerp.base.Preferences = openerp.base.Widget.extend({
});
-openerp.base.ImportExport = openerp.base.Controller.extend({
+openerp.base.ImportExport = openerp.base.Widget.extend({
});
-openerp.base.WebClient = openerp.base.Controller.extend({
+openerp.base.WebClient = openerp.base.Widget.extend({
init: function(element_id) {
this._super(null, element_id);
@@ -879,7 +969,7 @@ openerp.base.WebClient = openerp.base.Controller.extend({
this.crashmanager.start(false);
// Do you autorize this ? will be replaced by notify() in controller
- openerp.base.Controller.prototype.notification = new openerp.base.Notification(this, "oe_notification");
+ openerp.base.Widget.prototype.notification = new openerp.base.Notification(this, "oe_notification");
this.header = new openerp.base.Header(this, "oe_header");
this.login = new openerp.base.Login(this, "oe_login");
@@ -891,6 +981,7 @@ openerp.base.WebClient = openerp.base.Controller.extend({
this.menu = new openerp.base.Menu(this, "oe_menu", "oe_secondary_menu");
this.menu.on_action.add(this.on_menu_action);
+
},
start: function() {
this.session.start();
diff --git a/addons/base/static/src/js/controller.js b/addons/base/static/src/js/controller.js
deleted file mode 100644
index c9ca3193e70..00000000000
--- a/addons/base/static/src/js/controller.js
+++ /dev/null
@@ -1,248 +0,0 @@
-/*---------------------------------------------------------
- * OpenERP controller framework
- *--------------------------------------------------------*/
-
-openerp.base.controller = function(instance) {
-/**
- * John Resig Class with factory improvement
- */
-(function() {
- var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
- // The base Class implementation (does nothing)
- this.Class = function(){};
-
- // Create a new Class that inherits from this class
- Class.extend = function(prop) {
- var _super = this.prototype;
-
- // Instantiate a base class (but only create the instance,
- // don't run the init constructor)
- initializing = true;
- var prototype = new this();
- initializing = false;
-
- // Copy the properties over onto the new prototype
- for (var name in prop) {
- // Check if we're overwriting an existing function
- prototype[name] = typeof prop[name] == "function" &&
- typeof _super[name] == "function" && fnTest.test(prop[name]) ?
- (function(name, fn){
- return function() {
- var tmp = this._super;
-
- // Add a new ._super() method that is the same method
- // but on the super-class
- this._super = _super[name];
-
- // The method only need to be bound temporarily, so we
- // remove it when we're done executing
- var ret = fn.apply(this, arguments);
- this._super = tmp;
-
- return ret;
- };
- })(name, prop[name]) :
- prop[name];
- }
-
- // The dummy class constructor
- function Class() {
- // All construction is actually done in the init method
- if ( !initializing && this.init ) {
- var ret = this.init.apply(this, arguments);
- if (ret) { return ret; }
- }
- return this;
- }
-
- // Populate our constructed prototype object
- Class.prototype = prototype;
-
- // Enforce the constructor to be what we expect
- Class.constructor = Class;
-
- // And make this class extendable
- Class.extend = arguments.callee;
-
- return Class;
- };
-})();
-
-// todo change john resig class to keep window clean
-instance.base.Class = window.Class
-
-instance.base.callback = function(obj, method) {
- var callback = function() {
- var args = Array.prototype.slice.call(arguments);
- var r;
- for(var i = 0; i < callback.callback_chain.length; i++) {
- var c = callback.callback_chain[i];
- if(c.unique) {
- callback.callback_chain.splice(i, 1);
- i -= 1;
- }
- r = c.callback.apply(c.self, c.args.concat(args));
- // TODO special value to stop the chain
- // openerp.base.callback_stop
- }
- return r;
- };
- callback.callback_chain = [];
- callback.add = function(f) {
- if(typeof(f) == 'function') {
- f = { callback: f, args: Array.prototype.slice.call(arguments, 1) };
- }
- f.self = f.self || null;
- f.args = f.args || [];
- f.unique = !!f.unique;
- if(f.position == 'last') {
- callback.callback_chain.push(f);
- } else {
- callback.callback_chain.unshift(f);
- }
- return callback;
- };
- callback.add_first = function(f) {
- return callback.add.apply(null,arguments);
- };
- callback.add_last = function(f) {
- return callback.add({
- callback: f,
- args: Array.prototype.slice.call(arguments, 1),
- position: "last"
- });
- };
-
- return callback.add({
- callback: method,
- self:obj,
- args:Array.prototype.slice.call(arguments, 2)
- });
-};
-
-/**
- * Generates an inherited class that replaces all the methods by null methods (methods
- * that does nothing and always return undefined).
- *
- * @param {Class} claz
- * @param {dict} add Additional functions to override.
- * @return {Class}
- */
-instance.base.generate_null_object_class = function(claz, add) {
- var newer = {};
- var copy_proto = function(prototype) {
- for (var name in prototype) {
- if(typeof prototype[name] == "function") {
- newer[name] = function() {};
- }
- }
- if (prototype.prototype)
- copy_proto(prototype.prototype);
- };
- copy_proto(claz.prototype);
- newer.init = instance.base.Controller.prototype.init;
- var tmpclass = claz.extend(newer);
- return tmpclass.extend(add || {});
-};
-
-/**
- * OpenERP Controller
- * TODO merge BaseWidget with Controller
- */
-instance.base.Controller = instance.base.Class.extend( /** @lends instance.base.Controller# */{
- /**
- * @constructs
- * rpc operations, event binding and callback calling should be done in
- * start() instead of init so that events can be hooked in between.
- */
- init: function(parent, element_id) {
- this.element_id = element_id;
- this.$element = $('#' + element_id);
- if (element_id) {
- instance.screen[element_id] = this;
- }
- // save the parent children relationship
- this.controller_parent = parent;
- this.controller_children = [];
- if(parent && parent.controller_children) {
- parent.controller_children.push(this);
- }
- // backward compatibility
- this.parent = this.controller_parent;
- this.children = this.controller_children;
-
- // Transform on_* method into openerp.base.callbacks
- for (var name in this) {
- if(typeof(this[name]) == "function") {
- this[name].debug_name = name;
- // bind ALL function to this not only on_and _do ?
- if((/^on_|^do_/).test(name)) {
- this[name] = instance.base.callback(this, this[name]);
- }
- }
- }
- },
- /**
- * Event binding, rpc and callback calling required to initialize the
- * object should happen here
- *
- * Returns a promise object letting callers (subclasses and direct callers)
- * know when this component is done starting
- *
- * @returns {jQuery.Deferred}
- */
- start: function() {
- // returns an already fulfilled promise. Maybe we could return nothing?
- // $.when can take non-deferred and in that case it simply considers
- // them all as fulfilled promises.
- // But in thise case we *have* to ensure callers use $.when and don't
- // try to call deferred methods on this return value.
- return $.Deferred().done().promise();
- },
- stop: function() {
- if (this.parent && this.parent.children) {
- this.parent.children = _.without(this.parent.children, this);
- this.parent.controller_children = this.parent.children;
- }
- this.parent = null;
- this.controller_parent = null;
- },
- log: function() {
- var args = Array.prototype.slice.call(arguments);
- var caller = arguments.callee.caller;
- // TODO add support for line number using
- // https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
- // args.unshift("" + caller.debug_name);
- this.on_log.apply(this,args);
- },
- on_log: function() {
- if(window.openerp.debug || (window.location.search.indexOf('?debug') !== -1)) {
- var notify = false;
- var body = false;
- if(window.console) {
- console.log(arguments);
- } else {
- body = true;
- }
- var a = Array.prototype.slice.call(arguments, 0);
- for(var i = 0; i < a.length; i++) {
- var v = a[i]==null ? "null" : a[i].toString();
- if(i==0) {
- notify = v.match(/^not/);
- body = v.match(/^bod/);
- }
- if(body) {
- $('').text(v).appendTo($('body'));
- }
- if(notify && this.notification) {
- this.notification.notify("Logging:",v);
- }
- }
- }
-
- }
-});
-
-};
-
-// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/base/static/src/js/core.js b/addons/base/static/src/js/core.js
new file mode 100644
index 00000000000..e57d79e40bb
--- /dev/null
+++ b/addons/base/static/src/js/core.js
@@ -0,0 +1,572 @@
+/*---------------------------------------------------------
+ * OpenERP controller framework
+ *--------------------------------------------------------*/
+
+openerp.base.core = function(openerp) {
+/**
+ * John Resig Class with factory improvement
+ */
+(function() {
+ var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
+ // The base Class implementation (does nothing)
+ openerp.base.Class = function(){};
+
+ // Create a new Class that inherits from this class
+ openerp.base.Class.extend = function(prop) {
+ var _super = this.prototype;
+
+ // Instantiate a base class (but only create the instance,
+ // don't run the init constructor)
+ initializing = true;
+ var prototype = new this();
+ initializing = false;
+
+ // Copy the properties over onto the new prototype
+ for (var name in prop) {
+ // Check if we're overwriting an existing function
+ prototype[name] = typeof prop[name] == "function" &&
+ typeof _super[name] == "function" && fnTest.test(prop[name]) ?
+ (function(name, fn){
+ return function() {
+ var tmp = this._super;
+
+ // Add a new ._super() method that is the same method
+ // but on the super-class
+ this._super = _super[name];
+
+ // The method only need to be bound temporarily, so we
+ // remove it when we're done executing
+ var ret = fn.apply(this, arguments);
+ this._super = tmp;
+
+ return ret;
+ };
+ })(name, prop[name]) :
+ prop[name];
+ }
+
+ // The dummy class constructor
+ function Class() {
+ // All construction is actually done in the init method
+ if ( !initializing && this.init ) {
+ var ret = this.init.apply(this, arguments);
+ if (ret) { return ret; }
+ }
+ return this;
+ }
+
+ // Populate our constructed prototype object
+ Class.prototype = prototype;
+
+ // Enforce the constructor to be what we expect
+ Class.constructor = Class;
+
+ // And make this class extendable
+ Class.extend = arguments.callee;
+
+ return Class;
+ };
+})();
+
+openerp.base.callback = function(obj, method) {
+ var callback = function() {
+ var args = Array.prototype.slice.call(arguments);
+ var r;
+ for(var i = 0; i < callback.callback_chain.length; i++) {
+ var c = callback.callback_chain[i];
+ if(c.unique) {
+ callback.callback_chain.splice(i, 1);
+ i -= 1;
+ }
+ r = c.callback.apply(c.self, c.args.concat(args));
+ // TODO special value to stop the chain
+ // openerp.base.callback_stop
+ }
+ return r;
+ };
+ callback.callback_chain = [];
+ callback.add = function(f) {
+ if(typeof(f) == 'function') {
+ f = { callback: f, args: Array.prototype.slice.call(arguments, 1) };
+ }
+ f.self = f.self || null;
+ f.args = f.args || [];
+ f.unique = !!f.unique;
+ if(f.position == 'last') {
+ callback.callback_chain.push(f);
+ } else {
+ callback.callback_chain.unshift(f);
+ }
+ return callback;
+ };
+ callback.add_first = function(f) {
+ return callback.add.apply(null,arguments);
+ };
+ callback.add_last = function(f) {
+ return callback.add({
+ callback: f,
+ args: Array.prototype.slice.call(arguments, 1),
+ position: "last"
+ });
+ };
+
+ return callback.add({
+ callback: method,
+ self:obj,
+ args:Array.prototype.slice.call(arguments, 2)
+ });
+};
+
+/**
+ * Generates an inherited class that replaces all the methods by null methods (methods
+ * that does nothing and always return undefined).
+ *
+ * @param {Class} claz
+ * @param {dict} add Additional functions to override.
+ * @return {Class}
+ */
+openerp.base.generate_null_object_class = function(claz, add) {
+ var newer = {};
+ var copy_proto = function(prototype) {
+ for (var name in prototype) {
+ if(typeof prototype[name] == "function") {
+ newer[name] = function() {};
+ }
+ }
+ if (prototype.prototype)
+ copy_proto(prototype.prototype);
+ };
+ copy_proto(claz.prototype);
+ newer.init = openerp.base.Widget.prototype.init;
+ var tmpclass = claz.extend(newer);
+ return tmpclass.extend(add || {});
+};
+
+/**
+ * Base error for lookup failure
+ *
+ * @class
+ */
+openerp.base.NotFound = openerp.base.Class.extend( /** @lends openerp.base.NotFound# */ {
+});
+openerp.base.KeyNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.KeyNotFound# */ {
+ /**
+ * Thrown when a key could not be found in a mapping
+ *
+ * @constructs
+ * @extends openerp.base.NotFound
+ * @param {String} key the key which could not be found
+ */
+ init: function (key) {
+ this.key = key;
+ },
+ toString: function () {
+ return "The key " + this.key + " was not found";
+ }
+});
+openerp.base.ObjectNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.ObjectNotFound# */ {
+ /**
+ * Thrown when an object path does not designate a valid class or object
+ * in the openerp hierarchy.
+ *
+ * @constructs
+ * @extends openerp.base.NotFound
+ * @param {String} path the invalid object path
+ */
+ init: function (path) {
+ this.path = path;
+ },
+ toString: function () {
+ return "Could not find any object of path " + this.path;
+ }
+});
+openerp.base.Registry = openerp.base.Class.extend( /** @lends openerp.base.Registry# */ {
+ /**
+ * Stores a mapping of arbitrary key (strings) to object paths (as strings
+ * as well).
+ *
+ * Resolves those paths at query time in order to always fetch the correct
+ * object, even if those objects have been overloaded/replaced after the
+ * registry was created.
+ *
+ * An object path is simply a dotted name from the openerp root to the
+ * object pointed to (e.g. ``"openerp.base.Session"`` for an OpenERP
+ * session object).
+ *
+ * @constructs
+ * @param {Object} mapping a mapping of keys to object-paths
+ */
+ init: function (mapping) {
+ this.map = mapping || {};
+ },
+ /**
+ * Retrieves the object matching the provided key string.
+ *
+ * @param {String} key the key to fetch the object for
+ * @returns {Class} the stored class, to initialize
+ *
+ * @throws {openerp.base.KeyNotFound} if the object was not in the mapping
+ * @throws {openerp.base.ObjectNotFound} if the object path was invalid
+ */
+ get_object: function (key) {
+ var path_string = this.map[key];
+ if (path_string === undefined) {
+ throw new openerp.base.KeyNotFound(key);
+ }
+
+ var object_match = openerp;
+ var path = path_string.split('.');
+ // ignore first section
+ for(var i=1; i').text(v).appendTo($('body'));
+ }
+ if(notify && this.notification) {
+ this.notification.notify("Logging:",v);
+ }
+ }
+ }
+ }
+});
+
+/**
+ * Base class for all visual components. Provides a lot of functionalities helpful
+ * for the management of a part of the DOM.
+ *
+ * Widget handles:
+ * - Rendering with QWeb.
+ * - Life-cycle management and parenting (when a parent is destroyed, all its children are
+ * destroyed too).
+ * - Insertion in DOM.
+ *
+ * Widget also extends SessionAware for ease of use.
+ *
+ * Guide to create implementations of the Widget class:
+ * ==============================================
+ *
+ * Here is a sample child class:
+ *
+ * MyWidget = openerp.base.Widget.extend({
+ * // the name of the QWeb template to use for rendering
+ * template: "MyQWebTemplate",
+ * // identifier prefix, it is useful to put an obvious one for debugging
+ * identifier_prefix: 'my-id-prefix-',
+ *
+ * init: function(parent) {
+ * this._super(parent);
+ * // stuff that you want to init before the rendering
+ * },
+ * start: function() {
+ * this._super();
+ * // stuff you want to make after the rendering, `this.$element` holds a correct value
+ * this.$element.find(".my_button").click(/* an example of event binding * /);
+ *
+ * // if you have some asynchronous operations, it's a good idea to return
+ * // a promise in start()
+ * var promise = this.rpc(...);
+ * return promise;
+ * }
+ * });
+ *
+ * Now this class can simply be used with the following syntax:
+ *
+ * var my_widget = new MyWidget(this);
+ * my_widget.appendTo($(".some-div"));
+ *
+ * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
+ * DOM inside the ".some-div" div and its events were binded.
+ *
+ * And of course, when you don't need that widget anymore, just do:
+ *
+ * my_widget.stop();
+ *
+ * That will kill the widget in a clean way and erase its content from the dom.
+ */
+openerp.base.Widget = openerp.base.SessionAware.extend({
+ /**
+ * 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.
+ *
+ * @type string
+ */
+ template: null,
+ /**
+ * The prefix used to generate an id automatically. Should be redefined in
+ * subclasses. If it is not defined, a generic identifier will be used.
+ *
+ * @type string
+ */
+ identifier_prefix: 'generic-identifier-',
+ /**
+ * Construct the widget and set its parent if a parent is given.
+ *
+ * @constructs
+ * @param {openerp.base.Widget} parent Binds the current instance to the given Widget instance.
+ * When that widget is destroyed by calling stop(), 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, /** @deprecated */ element_id) {
+ this._super((parent || {}).session);
+ // if given an element_id, try to get the associated DOM element and save
+ // a reference in this.$element. Else just generate a unique identifier.
+ this.element_id = element_id;
+ this.element_id = this.element_id || _.uniqueId(this.identifier_prefix);
+ var tmp = document.getElementById(this.element_id);
+ this.$element = tmp ? $(tmp) : undefined;
+
+ this.widget_parent = parent;
+ this.widget_children = [];
+ if(parent && parent.widget_children) {
+ parent.widget_children.push(this);
+ }
+ // useful to know if the widget was destroyed and should not be used anymore
+ this.widget_is_stopped = false;
+ },
+ /**
+ * Render the current widget and appends it to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ appendTo: function(target) {
+ var self = this;
+ return this._render_and_insert(function(t) {
+ self.$element.appendTo(t);
+ }, target);
+ },
+ /**
+ * Render the current widget and prepends it to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ prependTo: function(target) {
+ var self = this;
+ return this._render_and_insert(function(t) {
+ self.$element.prependTo(t);
+ }, target);
+ },
+ /**
+ * Render the current widget and inserts it after to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ insertAfter: function(target) {
+ var self = this;
+ return this._render_and_insert(function(t) {
+ self.$element.insertAfter(t);
+ }, target);
+ },
+ /**
+ * Render the current widget and inserts it before to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ insertBefore: function(target) {
+ var self = this;
+ return this._render_and_insert(function(t) {
+ self.$element.insertBefore(t);
+ }, target);
+ },
+ _render_and_insert: function(insertion, target) {
+ var rendered = this.render();
+ this.$element = $(rendered);
+ if (target instanceof openerp.base.Widget)
+ target = target.$element;
+ insertion(target);
+ return this.start();
+ },
+ /**
+ * Renders the widget using QWeb, `this.template` must be defined.
+ * The context given to QWeb contains the "widget" key that references `this`.
+ *
+ * @param {Object} additional Additional context arguments to pass to the template.
+ */
+ render: function (additional) {
+ return QWeb.render(this.template, _.extend({widget: this}, additional || {}));
+ },
+ /**
+ * Method called after rendering. Mostly used to bind actions, perform asynchronous
+ * calls, etc...
+ *
+ * By convention, the method should return a promise to inform the caller when
+ * this widget has been initialized.
+ *
+ * @returns {jQuery.Deferred}
+ */
+ start: function() {
+ if (!this.$element) {
+ var tmp = document.getElementById(this.element_id);
+ this.$element = tmp ? $(tmp) : undefined;
+ }
+ return $.Deferred().done().promise();
+ },
+ /**
+ * Destroys the current widget, also destory all its children before destroying itself.
+ */
+ stop: function() {
+ _.each(_.clone(this.widget_children), function(el) {
+ el.stop();
+ });
+ if(this.$element != null) {
+ this.$element.remove();
+ }
+ if (this.widget_parent && this.widget_parent.widget_children) {
+ this.widget_parent.widget_children = _.without(this.widget_parent.widget_children, this);
+ }
+ this.widget_parent = null;
+ this.widget_is_stopped = true;
+ },
+ /**
+ * Inform the action manager to do an action. Of course, this suppose that
+ * the action manager can be found amongst the ancestors of the current widget.
+ * If that's not the case this method will simply return `false`.
+ */
+ do_action: function(action, on_finished) {
+ if (this.widget_parent) {
+ return this.widget_parent.do_action(action, on_finished);
+ }
+ return false;
+ },
+ rpc: function(url, data, success, error) {
+ var def = $.Deferred().then(success, error);
+ var self = this;
+ this._super(url, data). then(function() {
+ if (!self.widget_is_stopped)
+ def.resolve.apply(def, arguments);
+ }, function() {
+ if (!self.widget_is_stopped)
+ def.reject.apply(def, arguments);
+ });
+ return def.promise();
+ }
+});
+
+/**
+ * @deprecated
+ * For retro compatibility only, the only difference with is that render() uses
+ * directly `this` instead of context with a "widget" key.
+ */
+openerp.base.OldWidget = openerp.base.Widget.extend({
+ render: function (additional) {
+ return QWeb.render(this.template, _.extend(_.extend({}, this), additional || {}));
+ }
+});
+
+};
+// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js
index 080f4409fa1..3b2c88acee1 100644
--- a/addons/base/static/src/js/data.js
+++ b/addons/base/static/src/js/data.js
@@ -18,7 +18,7 @@ openerp.base.serialize_sort = function (criterion) {
}).join(', ');
};
-openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.base.DataGroup# */{
+openerp.base.DataGroup = openerp.base.Widget.extend( /** @lends openerp.base.DataGroup# */{
/**
* Management interface between views and grouped collections of OpenERP
* records.
@@ -30,7 +30,7 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas
* content of the current grouping level.
*
* @constructs
- * @extends openerp.base.Controller
+ * @extends openerp.base.Widget
*
* @param {openerp.base.Session} session Current OpenERP session
* @param {String} model name of the model managed by this DataGroup
@@ -232,13 +232,13 @@ openerp.base.StaticDataGroup = openerp.base.GrouplessDataGroup.extend( /** @lend
}
});
-openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.DataSet# */{
+openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.DataSet# */{
/**
* DateaManagement interface between views and the collection of selected
* OpenERP records (represents the view's state?)
*
* @constructs
- * @extends openerp.base.Controller
+ * @extends openerp.base.Widget
*
* @param {String} model the OpenERP model this dataset will manage
*/
@@ -291,14 +291,18 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
* Read the indexed record.
*/
read_index: function (fields, callback) {
+ var def = $.Deferred().then(callback);
if (_.isEmpty(this.ids)) {
- return $.Deferred().reject().promise();
+ def.reject();
} else {
fields = fields || false;
- return this.read_ids([this.ids[this.index]], fields, function(records) {
- callback(records[0]);
+ return this.read_ids([this.ids[this.index]], fields).then(function(records) {
+ def.resolve(records[0]);
+ }, function() {
+ def.reject.apply(def, arguments);
});
}
+ return def.promise();
},
default_get: function(fields, callback) {
return this.rpc('/base/dataset/default_get', {
@@ -500,24 +504,33 @@ openerp.base.BufferedDataSet = openerp.base.DataSetStatic.extend({
this.to_create.push(cached);
this.cache.push(cached);
var to_return = $.Deferred().then(callback);
- setTimeout(function() {to_return.resolve({result: cached.id});}, 0);
+ to_return.resolve({result: cached.id});
return to_return.promise();
},
write: function (id, data, callback) {
var self = this;
var record = _.detect(this.to_create, function(x) {return x.id === id;});
record = record || _.detect(this.to_write, function(x) {return x.id === id;});
+ var dirty = false;
if (record) {
+ for (k in data) {
+ if (record.values[k] === undefined || record.values[k] !== data[k]) {
+ dirty = true;
+ break;
+ }
+ }
$.extend(record.values, data);
} else {
+ dirty = true;
record = {id: id, values: data};
self.to_write.push(record);
}
var cached = _.detect(this.cache, function(x) {return x.id === id;});
$.extend(cached.values, record.values);
- this.on_change();
+ if (dirty)
+ this.on_change();
var to_return = $.Deferred().then(callback);
- setTimeout(function () {to_return.resolve({result: true});}, 0);
+ to_return.resolve({result: true});
return to_return.promise();
},
unlink: function(ids, callback, error_callback) {
diff --git a/addons/base/static/src/js/data_export.js b/addons/base/static/src/js/data_export.js
new file mode 100644
index 00000000000..ad440302c69
--- /dev/null
+++ b/addons/base/static/src/js/data_export.js
@@ -0,0 +1,402 @@
+openerp.base.data_export = function(openerp) {
+openerp.base.DataExport = openerp.base.Dialog.extend({
+ init: function(parent, dataset) {
+ this._super(parent);
+ this.dataset = dataset;
+ },
+ start: function() {
+ var self = this;
+ self._super(false);
+ self.template = 'ExportTreeView';
+ self.dialog_title = "Export Data";
+ self.open({
+ modal: true,
+ width: '55%',
+ height: 'auto',
+ position: 'top',
+ buttons : {
+ "Close" : function() {
+ self.close();
+ },
+ "Export To File" : function() {
+ self.on_click_export_data();
+ }
+ },
+ close: function(event, ui){ self.close();}
+ });
+ self.on_show_exists_export_list();
+ self.$element.removeClass('ui-dialog-content ui-widget-content');
+ self.$element.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');
+ 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.$element.find('#remove_field').click(function() {
+ self.$element.find('#fields_list option:selected').remove();
+ });
+ self.$element.find('#remove_all_field').click(function() {
+ self.$element.find('#fields_list option').remove();
+ });
+ self.$element.find('#export_new_list').click(function() {
+ self.on_show_save_list();
+ });
+ var import_comp = self.$element.find('#import_compat option:selected').val(),
+ params = {
+ import_compat: parseInt(import_comp)
+ };
+ self.rpc('/base/export/get_fields', { model: self.dataset.model, params: params }, self.on_show_data);
+
+ self.$element.find('#import_compat').change(function() {
+ self.$element.find('#fields_list option').remove();
+ self.$element.find('#field-tree-structure').remove();
+ var import_comp = self.$element.find("#import_compat option:selected").val();
+ if (import_comp) {
+ var params = {
+ import_compat: parseInt(import_comp)
+ }
+ self.rpc("/base/export/get_fields", { model: self.dataset.model, params: params}, self.on_show_data);
+ }
+ });
+ },
+ on_show_exists_export_list: function() {
+ var self = this;
+ if (self.$element.find('#saved_export_list').is(':hidden')) {
+ self.$element.find('#ExistsExportList').show();
+ } else {
+ this.rpc('/base/export/exist_export_lists', { 'model': this.dataset.model}, function(export_list) {
+ if (export_list.length) {
+ 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();
+ if (export_id) {
+ self.rpc('/base/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');
+ if (select_exp.val()) {
+ self.rpc('/base/export/delete_export', { export_id: parseInt(select_exp.val())}, {});
+ select_exp.remove();
+ if (self.$element.find('#saved_export_list option').length <= 1) {
+ self.$element.find('#ExistsExportList').hide();
+ }
+ }
+ });
+ }
+ });
+ }
+ },
+ do_load_export_field: function(field_list) {
+ var export_node = this.$element.find("#fields_list");
+ for (var key in field_list) {
+ export_node.append(new Option(field_list[key], key));
+ }
+ },
+ on_show_save_list: function() {
+ var self = this;
+ var current_node = self.$element.find("#savenewlist");
+ if (!(current_node.find("label")).length) {
+ current_node.append(QWeb.render('ExportNewList'));
+ current_node.find("#add_export_list").click(function() {
+ var value = current_node.find("#savelist_name").val();
+ if (value) {
+ self.do_save_export_list(value);
+ } else {
+ alert("Pleae Enter Save Field List Name");
+ }
+ });
+ } else {
+ if (current_node.is(':hidden')) {
+ current_node.show();
+ current_node.find("#savelist_name").val("");
+ } else {
+ current_node.hide();
+ }
+ }
+ },
+ do_save_export_list: function(value) {
+ var self = this;
+ var export_field = self.get_fields();
+ if (export_field.length) {
+ self.rpc("/base/export/save_export_lists", {"model": self.dataset.model, "name":value, "field_list":export_field}, function(exp_id) {
+ if (exp_id) {
+ if (self.$element.find("#saved_export_list").length > 0) {
+ self.$element.find("#saved_export_list").append(new Option(value, exp_id));
+ } else {
+ self.on_show_exists_export_list();
+ }
+ if (self.$element.find("#saved_export_list").is(":hidden")) {
+ self.on_show_exists_export_list();
+ }
+ }
+ });
+ self.on_show_save_list();
+ self.$element.find("#fields_list option").remove();
+ }
+ },
+ on_click: function(id, result) {
+ var self = this;
+ self.field_id = id.split("-")[1];
+ var is_loaded = 0;
+ _.each(result, function(record) {
+ if (record['id'] == self.field_id && (record['children']).length >= 1) {
+ var model = record['params']['model'],
+ prefix = record['params']['prefix'],
+ name = record['params']['name'];
+ $(record['children']).each(function(e, childid) {
+ if (self.$element.find("tr[id='treerow-" + childid + "']").length > 0) {
+ if (self.$element.find("tr[id='treerow-" + childid + "']").is(':hidden')) {
+ is_loaded = -1;
+ } else {
+ is_loaded++;
+ }
+ }
+ });
+ if (is_loaded == 0) {
+ if (self.$element.find("tr[id='treerow-" + self.field_id +"']").find('img').attr('src') === '/base/static/src/img/expand.gif') {
+ if (model) {
+ var import_comp = self.$element.find("#import_compat option:selected").val();
+ var params = {
+ import_compat: parseInt(import_comp),
+ parent_field_type : record['field_type']
+ }
+ self.rpc("/base/export/get_fields", {
+ model: model,
+ prefix: prefix,
+ name: name,
+ field_parent : self.field_id,
+ params: params
+ }, function(results) {
+ self.on_show_data(results);
+ });
+ }
+ }
+ } else if (is_loaded > 0) {
+ self.showcontent(self.field_id, true);
+ } else {
+ self.showcontent(self.field_id, false);
+ }
+ }
+ });
+ },
+ on_show_data: function(result) {
+ var self = this;
+ var imp_cmpt = parseInt(self.$element.find("#import_compat option:selected").val());
+ var current_tr = self.$element.find("tr[id='treerow-" + self.field_id + "']");
+ if (current_tr.length >= 1) {
+ current_tr.find('img').attr('src','/base/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}));
+ }
+ _.each(result, function(record) {
+ if ((record.field_type == "one2many") && imp_cmpt) {
+ var o2m_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
+ o2m_fld.addClass("oe_export_readonlyfield");
+ }
+ if ((record.required == true) || record.required == "True") {
+ var required_fld = self.$element.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.on_click(this.id, result);
+ });
+
+ self.$element.find("tr[id='treerow-" + record.id + "']").click(function(e) {
+ if (e.shiftKey == true) {
+ 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).addClass("ui-selected");
+ } else {
+ if (this.rowIndex >=self.row_index) {
+ for (i = (self.row_index-1); i < this.rowIndex; i++) {
+ scnd_click = self.$element.find("tr[id^='treerow-']")[i];
+ if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(scnd_click).addClass("ui-selected");
+ }
+ }
+ } else {
+ for (i = (self.row_index-1); i >= (this.rowIndex-1); i--) {
+ scnd_click = self.$element.find("tr[id^='treerow-']")[i];
+ if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(scnd_click).addClass("ui-selected");
+ }
+ }
+ }
+ }
+ }
+ self.row_index = this.rowIndex;
+
+ self.$element.find("tr[id='treerow-" + record.id + "']").keyup(function(e) {
+ self.row_index = 0;
+ });
+ var o2m_selection = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
+ if ($(o2m_selection).hasClass("oe_export_readonlyfield")) {
+ return false;
+ }
+ var selected = self.$element.find("tr.ui-selected");
+ if ($(this).hasClass("ui-selected") && (e.ctrlKey == true)) {
+ $(this).find('a').blur();
+ $(this).removeClass("ui-selected");
+ } else if ($(this).hasClass("ui-selected") && (e.ctrlKey == false) && (e.shiftKey == false)) {
+ selected.find('a').blur();
+ selected.removeClass("ui-selected");
+ $(this).find('a').focus();
+ $(this).addClass("ui-selected");
+ } else if (!$(this).hasClass("ui-selected") && (e.ctrlKey == false) && (e.shiftKey == false)) {
+ selected.find('a').blur();
+ selected.removeClass("ui-selected");
+ $(this).find('a').focus();
+ $(this).addClass("ui-selected");
+ } else if (!$(this).hasClass("ui-selected") && (e.ctrlKey == true)) {
+ $(this).find('a').focus();
+ $(this).addClass("ui-selected");
+ }
+ return false;
+ });
+
+ self.$element.find("tr[id='treerow-" + record.id + "']").keydown(function(e) {
+ var keyCode = e.keyCode || e.which;
+ arrow = {left: 37, up: 38, right: 39, down: 40 };
+ switch (keyCode) {
+ case arrow.left:
+ if ($(this).find('img').attr('src') === '/base/static/src/img/collapse.gif') {
+ self.on_click(this.id, result);
+ }
+ break;
+ case arrow.up:
+ var elem = this;
+ $(elem).removeClass("ui-selected");
+ while ($(elem).prev().is(":visible") == false) {
+ elem = $(elem).prev();
+ }
+ if (!$(elem).prev().find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(elem).prev().addClass("ui-selected");
+ }
+ $(elem).prev().find('a').focus();
+ break;
+ case arrow.right:
+ if ($(this).find('img').attr('src') == '/base/static/src/img/expand.gif') {
+ self.on_click(this.id, result);
+ }
+ break;
+ case arrow.down:
+ var elem = this;
+ $(elem).removeClass("ui-selected");
+ while($(elem).next().is(":visible") == false) {
+ elem = $(elem).next();
+ }
+ if (!$(elem).next().find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(elem).next().addClass("ui-selected");
+ }
+ $(elem).next().find('a').focus();
+ break;
+ }
+ });
+ self.$element.find("tr[id='treerow-" + record.id + "']").dblclick(function(e) {
+ var $o2m_selection = self.$element.find("tr[id^='treerow-" + record.id + "']").find('#tree-column');
+ if (!$o2m_selection.hasClass("oe_export_readonlyfield")) {
+ var field_id = $(this).find("a").attr("id");
+ if (field_id) {
+ self.add_field(field_id.split('-')[1], $(this).find("a").attr("string"));
+ }
+ }
+ });
+ });
+ self.$element.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")) {
+ self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]);
+ }
+ }
+ }
+ }
+ });
+ },
+ showcontent: function(id, flag) {
+ // show & hide the contents
+ var first_child = this.$element.find("tr[id='treerow-" + id + "']").find('img');
+ if (flag) {
+ first_child.attr('src', '/base/static/src/img/expand.gif');
+ }
+ else {
+ first_child.attr('src', '/base/static/src/img/collapse.gif');
+ }
+ var child_field = this.$element.find("tr[id^='treerow-" + id +"/']");
+ var child_len = (id.split("/")).length + 1;
+ for (var i = 0; i < child_field.length; i++) {
+ if (flag) {
+ $(child_field[i]).hide();
+ } else {
+ if (child_len == (child_field[i].id.split("/")).length) {
+ if ($(child_field[i]).find('img').attr('src') == '/base/static/src/img/collapse.gif') {
+ $(child_field[i]).find('img').attr('src', '/base/static/src/img/expand.gif');
+ }
+ $(child_field[i]).show();
+ }
+ }
+ }
+ },
+ 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) {
+ field_list.append(new Option(string, field_id));
+ }
+ },
+ get_fields: function() {
+ var export_field = [];
+ this.$element.find("#fields_list option").each(function() {
+ export_field.push($(this).val());
+ });
+ if (!export_field.length) {
+ alert('Please select fields to save export list...');
+ }
+ return export_field;
+ },
+ on_click_export_data: function() {
+ var self = this;
+ var export_field = {};
+ var flag = true;
+ self.$element.find("#fields_list option").each(function() {
+ export_field[$(this).val()] = $(this).text();
+ flag = false;
+ });
+ if (flag) {
+ alert('Please select fields to export...');
+ return;
+ }
+
+ var import_comp = self.$element.find("#import_compat option:selected").val(),
+ export_format = self.$element.find("#export_format").val();
+
+ self.rpc("/base/export/export_data", {
+ model: self.dataset.model,
+ fields: export_field,
+ ids: self.dataset.ids,
+ domain: self.dataset.domain,
+ import_compat: parseInt(import_comp),
+ export_format: export_format
+ }, function(data) {
+ window.location = "data:text/csv/excel;charset=utf8," + data;
+ self.close();
+ });
+ },
+ close: function() {
+ $(this.$dialog).remove();
+ this._super();
+ }
+});
+
+};
diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js
index 7b86ce9f372..aea78502bc1 100644
--- a/addons/base/static/src/js/form.js
+++ b/addons/base/static/src/js/form.js
@@ -1,7 +1,7 @@
openerp.base.form = function (openerp) {
openerp.base.views.add('form', 'openerp.base.FormView');
-openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{
+openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{
/**
* Indicates that this view is not searchable, and thus that no search
* view should be displayed (if there is one active).
@@ -19,7 +19,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
*/
init: function(parent, element_id, dataset, view_id, options) {
this._super(parent, element_id);
- this.view_manager = parent || new openerp.base.NullViewManager();
+ this.set_default_options();
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
@@ -30,8 +30,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.datarecord = {};
this.ready = false;
this.show_invalid = true;
- this.touched = false;
- this.flags = this.view_manager.flags || {};
+ this.dirty = false;
this.default_focus_field = null;
this.default_focus_button = null;
this.registry = openerp.base.form.widgets;
@@ -49,14 +48,15 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
return def.promise();
} else {
var context = new openerp.base.CompoundContext(this.dataset.get_context());
- if (this.view_manager.action && this.view_manager.action.context) {
- context.add(this.view_manager.action.context);
- }
return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id,
- toolbar:!!this.flags.sidebar, context: context}, this.on_loaded);
+ toolbar: this.options.sidebar, context: context}, this.on_loaded);
}
},
stop: function() {
+ if (this.sidebar) {
+ this.sidebar.attachments.stop();
+ this.sidebar.stop();
+ }
_.each(this.widgets, function(w) {
w.stop();
});
@@ -85,12 +85,17 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
$('' + openerp.base.json_node_to_xml(self.fields_view.arch, true) + '').dialog({ width: '95%', height: 600});
});
- if(this.view_manager.sidebar)
- this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
+ if (this.options.sidebar && this.options.sidebar_id) {
+ this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id);
+ this.sidebar.start();
+ this.sidebar.do_unfold();
+ this.sidebar.attachments = new openerp.base.form.SidebarAttachments(this.sidebar, this.sidebar.add_section('attachments', "Attachments"), this);
+ this.sidebar.add_toolbar(data.fields_view.toolbar);
+ this.set_common_sidebar_sections(this.sidebar);
+ }
this.has_been_loaded.resolve();
},
do_show: function () {
- var self = this;
var promise;
if (this.dataset.index === null) {
// null index means we should start a new record
@@ -98,13 +103,17 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
} else {
promise = this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
}
- self.$element.show();
- if(this.view_manager.sidebar)
- this.view_manager.sidebar.do_refresh(true);
+ this.$element.show();
+ if (this.sidebar) {
+ this.sidebar.$element.show();
+ }
return promise;
},
do_hide: function () {
this.$element.hide();
+ if (this.sidebar) {
+ this.sidebar.$element.hide();
+ }
},
on_record_loaded: function(record) {
if (!record) {
@@ -121,22 +130,22 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.$form_header.find('.oe_form_on_update').show();
this.$form_header.find('button.oe_form_button_new').show();
}
- this.touched = false;
+ this.dirty = false;
this.datarecord = record;
for (var f in this.fields) {
var field = this.fields[f];
- field.touched = false;
+ field.dirty = false;
field.set_value(this.datarecord[f] || false);
field.validate();
}
if (!record.id) {
// New record: Second pass in order to trigger the onchanges
- this.touched = true;
+ this.dirty = true;
this.show_invalid = false;
for (var f in record) {
var field = this.fields[f];
if (field) {
- field.touched = true;
+ field.dirty = true;
this.do_onchange(field);
}
}
@@ -144,7 +153,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.on_form_changed();
this.show_invalid = this.ready = true;
this.do_update_pager(record.id == null);
- this.do_update_sidebar();
+ if (this.sidebar) {
+ this.sidebar.attachments.do_update();
+ }
if (this.default_focus_field) {
this.default_focus_field.focus();
}
@@ -253,7 +264,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
processed.push(field.name);
if (field.get_value() != value) {
field.set_value(value);
- field.touched = true;
+ field.dirty = true;
if (_.indexOf(processed, field.name) < 0) {
this.do_onchange(field, processed);
}
@@ -301,37 +312,40 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
if (!this.ready) {
return false;
}
- var invalid = false,
+ var form_dirty = false,
+ form_invalid = false,
values = {},
first_invalid_field = null;
for (var f in this.fields) {
f = this.fields[f];
- if (f.invalid) {
- invalid = true;
+ if (!f.is_valid()) {
+ form_invalid = true;
f.update_dom();
if (!first_invalid_field) {
first_invalid_field = f;
}
- } else if (f.touched) {
+ } else if (f.is_dirty()) {
+ form_dirty = true;
values[f.name] = f.get_value();
}
}
- if (invalid) {
+ if (form_invalid) {
first_invalid_field.focus();
this.on_invalid();
return false;
- } else {
+ } else if (form_dirty) {
this.log("About to save", values);
if (!this.datarecord.id) {
- this.dataset.create(values, function(r) {
+ return this.dataset.create(values, function(r) {
self.on_created(r, success, prepend_on_create);
});
} else {
- this.dataset.write(this.datarecord.id, values, function(r) {
+ return this.dataset.write(this.datarecord.id, values, function(r) {
self.on_saved(r, success);
});
}
- return true;
+ } else {
+ return false;
}
},
do_save_edit: function() {
@@ -345,7 +359,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
on_invalid: function() {
var msg = "
";
_.each(this.fields, function(f) {
- if (f.invalid) {
+ if (!f.is_valid()) {
msg += "