[MERGE]Merge latest trunk.

bzr revid: kch@tinyerp.com-20110803055358-cpavcx8x2t60jtk0
This commit is contained in:
Kunal Chavda (OpenERP) 2011-08-03 11:23:58 +05:30
commit 933c576955
36 changed files with 5438 additions and 1363 deletions

View File

@ -6,8 +6,11 @@
'js' : [
"static/lib/datejs/date-en-US.js",
"static/lib/jquery/jquery-1.5.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",
@ -35,5 +39,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",
],
}

View File

@ -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,7 +12,9 @@ import openerpweb.ast
import openerpweb.nonliterals
import cherrypy
import xmlrpclib
import csv
# Should move to openerpweb.Xml2Json
class Xml2Json:
# xml2json-direct
@ -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
@ -903,18 +987,231 @@ class Action(openerpweb.Controller):
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)
class Import(View):
_cp_path = "/base/import"
@openerpweb.httprequest
def import_data(self, req, session_id, callback, model, id, csvfile, csvsep, csvdel, csvcode, csvskip, fields=[]):
import StringIO
context = req.session.eval_context(req.context)
modle_obj = req.session.model(model)
res = None
content = csvfile.file.read()
print "\n content++++++++++++++++++++++++++++",content
input=StringIO.StringIO(content)
limit = 0
data = []

View File

@ -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 = $('<div class="growlUI"></div>');
if (title) $m.append('<h1>'+title+'</h1>');
if (message) $m.append('<h2>'+message+'</h2>');
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: '<h1>Please wait...</h1>',
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)
? $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>')
: $('<div class="blockUI" style="display:none"></div>');
var lyr2 = opts.theme
? $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>')
: $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
var lyr3, s;
if (opts.theme && full) {
s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">' +
'<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>' +
'<div class="ui-widget-content ui-dialog-content"></div>' +
'</div>';
}
else if (opts.theme) {
s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">' +
'<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>' +
'<div class="ui-widget-content ui-dialog-content"></div>' +
'</div>';
}
else if (full) {
s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
}
else {
s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
}
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);

View File

@ -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 = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
$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(
$('<input type="hidden" name="'+n+'" />').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 <input type="image" /> 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&amp;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&amp;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:
*
* <form><fieldset>
* <input name="A" type="text" />
* <input name="A" type="text" />
* <input name="B" type="checkbox" value="B1" />
* <input name="B" type="checkbox" value="B2"/>
* <input name="C" type="radio" value="C1" />
* <input name="C" type="radio" value="C2" />
* </fieldset></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);

File diff suppressed because it is too large Load Diff

View File

@ -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"], input[type="password"], 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;
@ -1096,3 +1172,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;
}

View File

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

View File

@ -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);
}
if (openerp.base.import){
openerp.base.import(instance);
}

View File

@ -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<path.length; ++i) {
object_match = object_match[path[i]];
if (object_match === undefined) {
throw new openerp.base.ObjectNotFound(path_string);
}
}
return object_match;
},
/**
* Tries a number of keys, and returns the first object matching one of
* the keys.
*
* @param {Array} keys a sequence of keys to fetch the object for
* @returns {Class} the first class found matching an object
*
* @throws {openerp.base.KeyNotFound} if none of the keys was in the mapping
* @trows {openerp.base.ObjectNotFound} if a found object path was invalid
*/
get_any: function (keys) {
for (var i=0; i<keys.length; ++i) {
try {
return this.get_object(keys[i]);
} catch (e) {
if (e instanceof openerp.base.KeyNotFound) {
continue;
}
throw e;
}
}
throw new openerp.base.KeyNotFound(keys.join(','));
},
/**
* Adds a new key and value to the registry.
*
* This method can be chained.
*
* @param {String} key
* @param {String} object_path fully qualified dotted object path
* @returns {openerp.base.Registry} itself
*/
add: function (key, object_path) {
this.map[key] = object_path;
return this;
},
/**
* Creates and returns a copy of the current mapping, with the provided
* mapping argument added in (replacing existing keys if needed)
*
* @param {Object} [mapping={}] a mapping of keys to object-paths
*/
clone: function (mapping) {
return new openerp.base.Registry(
_.extend({}, this.map, mapping || {}));
}
});
/**
* OpenERP session aware controller
* a controller takes an already existing dom element and manage it
*/
openerp.base.Controller = openerp.base.Controller.extend( /** @lends openerp.base.Controller# */{
init: function(parent, element_id) {
this._super(parent, element_id);
if(this.controller_parent && this.controller_parent.session) {
this.session = this.controller_parent.session;
}
},
/**
* Performs a JSON-RPC call
*
* @param {String} url endpoint url
* @param {Object} data RPC parameters
* @param {Function} success RPC call success callback
* @param {Function} error RPC call error callback
* @returns {jQuery.Deferred} deferred object for the RPC call
*/
rpc: function(url, data, success, error) {
return this.session.rpc(url, data, success, error);
},
do_action: function(action, on_finished) {
return this.parent.do_action(action, on_finished);
}
});
/**
* OpenERP session aware widget
* A widget is a controller that doesnt take an element_id
* it render its own html render() that you should insert into the dom
* and bind it at start()
*/
openerp.base.BaseWidget = openerp.base.Controller.extend({
/**
* The name of the QWeb template that will be used for rendering. Must be
* redefined in subclasses or the 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 default identifier will be used.
*
* @type string
*/
identifier_prefix: 'generic-identifier',
/**
* Base class for widgets. Handle rendering (based on a QWeb template),
* identifier generation, parenting and destruction of the widget.
* Also initialize the identifier.
*
* @constructs
* @params {openerp.base.search.BaseWidget} parent The parent widget.
*/
init: function (parent) {
this._super(parent);
this.make_id(this.identifier_prefix);
},
/**
* Sets and returns a globally unique identifier for the widget.
*
* If a prefix is appended, the identifier will be appended to it.
*
* @params sections prefix sections, empty/falsy sections will be removed
*/
make_id: function () {
this.element_id = _.uniqueId(_.toArray(arguments).join('_'));
return this.element_id;
},
/**
* Render the widget. This.template must be defined.
* The content of the current object is passed as context to the template.
*
* @param {object} additional Additional context arguments to pass to the template.
*/
render: function (additional) {
return QWeb.render(this.template, _.extend({}, this, additional != null ? additional : {}));
},
/**
* "Starts" the widgets. Called at the end of the rendering, this allows
* to get a jQuery object referring to the DOM ($element attribute).
*/
start: function () {
this._super();
var tmp = document.getElementById(this.element_id);
this.$element = tmp ? $(tmp) : null;
},
/**
* "Stops" the widgets. Called when the view destroys itself, this
* lets the widgets clean up after themselves.
*/
stop: function () {
if(this.$element != null) {
this.$element.remove();
}
this._super();
}
});
openerp.base.Session = openerp.base.Controller.extend( /** @lends openerp.base.Session# */{
openerp.base.Session = openerp.base.Widget.extend( /** @lends openerp.base.Session# */{
/**
* @constructs
* @param element_id to use for exception reporting
@ -506,7 +272,7 @@ openerp.base.Session = openerp.base.Controller.extend( /** @lends openerp.base.S
}
});
openerp.base.Notification = openerp.base.Controller.extend({
openerp.base.Notification = openerp.base.Widget.extend({
init: function(parent, element_id) {
this._super(parent, element_id);
this.$element.notify({
@ -528,7 +294,7 @@ openerp.base.Notification = openerp.base.Controller.extend({
}
});
openerp.base.Dialog = openerp.base.BaseWidget.extend({
openerp.base.Dialog = openerp.base.OldWidget.extend({
dialog_title: "",
identifier_prefix: 'dialog',
init: function (parent, options) {
@ -544,8 +310,8 @@ openerp.base.Dialog = openerp.base.BaseWidget.extend({
max_height: '100%',
autoOpen: false,
buttons: {},
close: function () {
self.stop();
beforeClose: function () {
self.on_close();
}
};
for (var f in this) {
@ -609,7 +375,15 @@ openerp.base.Dialog = openerp.base.BaseWidget.extend({
this.set_options(options);
this.$dialog.dialog(this.options).dialog('open');
},
close: function() {
// Closes the dialog but leave it in a state where it could be opened again.
this.$dialog.dialog('close');
},
on_close: function() {
},
stop: function () {
// Destroy widget
this.close();
this.$dialog.dialog('destroy');
}
});
@ -643,14 +417,14 @@ openerp.base.CrashManager = openerp.base.Dialog.extend({
this.dialog_title = "OpenERP Error";
this.template = 'DialogTraceback';
this.open({
width: '80%',
height: '80%'
width: 'auto',
height: 'auto'
});
}
}
});
openerp.base.Loading = openerp.base.Controller.extend({
openerp.base.Loading = openerp.base.Widget.extend({
init: function(parent, element_id) {
this._super(parent, element_id);
this.count = 0;
@ -669,16 +443,314 @@ openerp.base.Loading = openerp.base.Controller.extend({
}
});
openerp.base.Database = openerp.base.Controller.extend({
openerp.base.Database = openerp.base.Widget.extend({
init: function(parent, element_id, option_id) {
this._super(parent, element_id);
this.$option_id = $('#' + option_id);
},
start: function() {
this.$element.html(QWeb.render("Database", this));
this.$element.closest(".openerp")
.removeClass("login-mode")
.addClass("database_block");
var self = this;
var fetch_db = this.rpc("/base/database/get_list", {}, function(result) {
self.db_list = result.db_list;
});
var fetch_langs = this.rpc("/base/session/get_lang_list", {}, function(result) {
if (result.error) {
self.display_error(result);
return;
}
self.lang_list = result.lang_list;
});
$.when(fetch_db, fetch_langs).then(function () {self.do_create();});
this.$element.find('#db-create').click(this.do_create);
this.$element.find('#db-drop').click(this.do_drop);
this.$element.find('#db-backup').click(this.do_backup);
this.$element.find('#db-restore').click(this.do_restore);
this.$element.find('#db-change-password').click(this.do_change_password);
this.$element.find('#back-to-login').click(function() {
self.stop();
});
},
stop: function () {
this.$option_id.empty();
this.$element
.find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login')
.unbind('click')
.end()
.closest(".openerp")
.addClass("login-mode")
.removeClass("database_block")
.end()
.empty();
},
/**
* Converts a .serializeArray() result into a dict. Does not bother folding
* multiple identical keys into an array, last key wins.
*
* @param {Array} array
*/
to_object: function (array) {
var result = {};
_(array).each(function (record) {
result[record.name] = record.value;
});
return result;
},
/**
* Waits until the new database is done creating, then unblocks the UI and
* logs the user in as admin
*
* @param {Number} db_creation_id identifier for the db-creation operation, used to fetch the current installation progress
* @param {Object} info info fields for this database creation
* @param {String} info.db name of the database being created
* @param {String} info.password super-admin password for the database
*/
wait_for_newdb: function (db_creation_id, info) {
var self = this;
self.rpc('/base/database/progress', {
id: db_creation_id,
password: info.password
}, function (result) {
var progress = result[0];
// I'd display a progress bar, but turns out the progress status
// the server report kind-of blows goats: it's at 0 for ~75% of
// the installation, then jumps to 75%, then jumps down to either
// 0 or ~40%, then back up to 75%, then terminates. Let's keep that
// mess hidden behind a not-very-useful but not overly weird
// message instead.
if (progress < 1) {
setTimeout(function () {
self.wait_for_newdb(db_creation_id, info);
}, 500);
return;
}
var admin = result[1][0];
setTimeout(function () {
self.stop();
self.widget_parent.do_login(
info.db, admin.login, admin.password);
$.unblockUI();
});
});
},
/**
* Displays an error dialog resulting from the various RPC communications
* failing over themselves
*
* @param {Object} error error description
* @param {String} error.title title of the error dialog
* @param {String} error.error message of the error dialog
*/
display_error: function (error) {
return $('<div>').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<cookies.length; ++i) {
var cookie = cookies[i].replace(/^\s*/, '');
if(!cookie.indexOf(cookie_name) === 0) { continue; }
var cookie_val = cookie.substring(cookie_length + 1);
if(parseInt(cookie_val, 10) !== token) { continue; }
// clear waiter
clearInterval(self.backup_timer);
// clear cookie
document.cookie = _.sprintf("%s=;expires=%s;path=/",
cookie_name, new Date().toGMTString());
if (cleanup) { cleanup(); }
}
}, 100);
},
do_backup: function() {
var self = this;
self.$option_id.html(QWeb.render("BackupDB", self));
self.$option_id.find("form[name=backup_db_form]").validate({
submitHandler: function (form) {
$.blockUI();
// need to detect when the file is done downloading (not used
// yet, but we'll need it to fix the UI e.g. with a throbber
// while dump is being generated), iframe load event only fires
// when the iframe content loads, so we need to go smarter:
// http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx
var $target = $('#backup-target'),
token = new Date().getTime();
if (!$target.length) {
$target = $('<iframe id="backup-target" style="display: none;">')
.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');
@ -691,7 +763,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() {
@ -699,7 +771,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() {
@ -710,13 +791,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) {
@ -750,7 +840,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);
},
@ -764,7 +854,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;
@ -845,16 +935,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);
@ -871,7 +961,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");
@ -883,6 +973,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();

View File

@ -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) {
$('<pre></pre>').text(v).appendTo($('body'));
}
if(notify && this.notification) {
this.notification.notify("Logging:",v);
}
}
}
}
});
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:

View File

@ -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<path.length; ++i) {
object_match = object_match[path[i]];
if (object_match === undefined) {
throw new openerp.base.ObjectNotFound(path_string);
}
}
return object_match;
},
/**
* Tries a number of keys, and returns the first object matching one of
* the keys.
*
* @param {Array} keys a sequence of keys to fetch the object for
* @returns {Class} the first class found matching an object
*
* @throws {openerp.base.KeyNotFound} if none of the keys was in the mapping
* @trows {openerp.base.ObjectNotFound} if a found object path was invalid
*/
get_any: function (keys) {
for (var i=0; i<keys.length; ++i) {
try {
return this.get_object(keys[i]);
} catch (e) {
if (e instanceof openerp.base.KeyNotFound) {
continue;
}
throw e;
}
}
throw new openerp.base.KeyNotFound(keys.join(','));
},
/**
* Adds a new key and value to the registry.
*
* This method can be chained.
*
* @param {String} key
* @param {String} object_path fully qualified dotted object path
* @returns {openerp.base.Registry} itself
*/
add: function (key, object_path) {
this.map[key] = object_path;
return this;
},
/**
* Creates and returns a copy of the current mapping, with the provided
* mapping argument added in (replacing existing keys if needed)
*
* @param {Object} [mapping={}] a mapping of keys to object-paths
*/
clone: function (mapping) {
return new openerp.base.Registry(
_.extend({}, this.map, mapping || {}));
}
});
/**
* Utility class that any class is allowed to extend to easy common manipulations.
*
* It provides rpc calls, callback on all methods preceded by "on_" or "do_" and a
* logging facility.
*/
openerp.base.SessionAware = openerp.base.Class.extend({
init: function(session) {
this.session = session;
// 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] = openerp.base.callback(this, this[name]);
}
}
}
},
/**
* Performs a JSON-RPC call
*
* @param {String} url endpoint url
* @param {Object} data RPC parameters
* @param {Function} success RPC call success callback
* @param {Function} error RPC call error callback
* @returns {jQuery.Deferred} deferred object for the RPC call
*/
rpc: function(url, data, success, error) {
return this.session.rpc(url, data, success, error);
},
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) {
$('<pre></pre>').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:

View File

@ -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', {
@ -492,24 +496,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) {

View File

@ -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();
}
});
};

View File

@ -20,7 +20,6 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi
init: function(parent, element_id, dataset, view_id, options) {
this._super(parent, element_id);
this.set_default_options();
this.view_manager = parent || new openerp.base.NullViewManager();
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
@ -31,8 +30,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi
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;
@ -50,9 +48,6 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi
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.options.sidebar, context: context}, this.on_loaded);
}
@ -93,9 +88,10 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi
if (this.options.sidebar && this.options.sidebar_id) {
this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id);
this.sidebar.start();
this.sidebar.attachments = new openerp.base.form.SidebarAttachments(this.sidebar, this.sidebar.add_section("Attachments"), this);
this.sidebar.add_toolbar(data.fields_view.toolbar);
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();
},
@ -134,22 +130,22 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi
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);
}
}
@ -268,7 +264,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi
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);
}
@ -316,37 +312,40 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi
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() {
@ -360,7 +359,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi
on_invalid: function() {
var msg = "<ul>";
_.each(this.fields, function(f) {
if (f.invalid) {
if (!f.is_valid()) {
msg += "<li>" + f.string + "</li>";
}
});
@ -443,7 +442,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi
/** @namespace */
openerp.base.form = {};
openerp.base.form.SidebarAttachments = openerp.base.Controller.extend({
openerp.base.form.SidebarAttachments = openerp.base.Widget.extend({
init: function(parent, element_id, form_view) {
this._super(parent, element_id);
this.view = form_view;
@ -550,7 +549,7 @@ openerp.base.form.compute_domain = function(expr, fields) {
return _.all(stack);
};
openerp.base.form.Widget = openerp.base.Controller.extend({
openerp.base.form.Widget = openerp.base.Widget.extend({
template: 'Widget',
init: function(view, node) {
this.view = view;
@ -716,6 +715,10 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
init: function(view, node) {
this._super(view, node);
this.template = "WidgetButton";
if (this.string) {
// We don't have button key bindings in the webclient
this.string = this.string.replace(/_/g, '');
}
if (node.attrs.default_focus == '1') {
// TODO fme: provide enter key binding to widgets
this.view.default_focus_button = this;
@ -727,7 +730,7 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
},
on_click: function(saved) {
var self = this;
if (!this.node.attrs.special && this.view.touched && saved !== true) {
if (!this.node.attrs.special && this.view.dirty && saved !== true) {
this.view.do_save(function() {
self.on_click(true);
});
@ -772,11 +775,12 @@ openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({
if (this.node.tag == 'label' && this.node.attrs.colspan) {
this.is_field_label = false;
this.template = "WidgetParagraph";
this.colspan = this.node.attrs.colspan;
} else {
this.is_field_label = true;
this.template = "WidgetLabel";
this.colspan = 1;
}
this.colspan = 1;
},
render: function () {
if (this['for'] && this.type !== 'label') {
@ -807,7 +811,7 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({
this.readonly = this.modifiers['readonly'] === true;
this.required = this.modifiers['required'] === true;
this.invalid = false;
this.touched = false;
this.dirty = false;
},
set_value: function(value) {
this.value = value;
@ -820,21 +824,29 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({
get_value: function() {
return this.value;
},
is_valid: function() {
return !this.invalid;
},
is_dirty: function() {
return this.dirty;
},
get_on_change_value: function() {
return this.get_value();
},
update_dom: function() {
this._super.apply(this, arguments);
this.$element.toggleClass('disabled', this.readonly);
this.$element.toggleClass('required', this.required);
if (this.view.show_invalid) {
this.$element.toggleClass('invalid', this.invalid);
if (!this.disable_utility_classes) {
this.$element.toggleClass('disabled', this.readonly);
this.$element.toggleClass('required', this.required);
if (this.view.show_invalid) {
this.$element.toggleClass('invalid', !this.is_valid());
}
}
},
on_ui_change: function() {
this.touched = this.view.touched = true;
this.dirty = this.view.dirty = true;
this.validate();
if (!this.invalid) {
if (this.is_valid()) {
this.set_value_from_ui();
this.view.do_onchange(this);
this.view.on_form_changed();
@ -932,7 +944,7 @@ openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({
this.$element.find('button').click(this.on_button_clicked);
},
on_button_clicked: function() {
if (!this.value || this.invalid) {
if (!this.value || !this.is_valid()) {
this.notification.warn("E-mail error", "Can't send email to invalid e-mail address");
} else {
location.href = 'mailto:' + this.value;
@ -973,7 +985,7 @@ openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
if (value === false || value === undefined) {
// As in GTK client, floats default to 0
value = 0;
this.touched = true;
this.dirty = true;
}
var show_value = value.toFixed(2);
this.$element.find('input').val(show_value);
@ -993,7 +1005,7 @@ openerp.base.form.FieldInteger = openerp.base.form.FieldFloat.extend({
if (value === false || value === undefined) {
// TODO fme: check if GTK client default integers to 0 (like it does with floats)
value = 0;
this.touched = true;
this.dirty = true;
}
var show_value = parseInt(value, 10);
this.$element.find('input').val(show_value);
@ -1080,7 +1092,7 @@ openerp.base.form.FieldFloatTime = openerp.base.form.FieldChar.extend({
if (value === false || value === undefined) {
// As in GTK client, floats default to 0
value = 0;
this.touched = true;
this.dirty = true;
}
var show_value = _.sprintf("%02d:%02d", Math.floor(value), Math.round((value % 1) * 60));
this.$element.find('input').val(show_value);
@ -1287,7 +1299,6 @@ openerp.base.form.dialog = function(content, options) {
options.autoOpen = true;
var dialog = new openerp.base.Dialog(null, options);
dialog.$dialog = $(content).dialog(dialog.options);
console.log(dialog.options)
return dialog.$dialog;
}
@ -1470,7 +1481,7 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
}, self.build_domain(),
new openerp.base.CompoundContext(self.build_context(), context || {}));
pop.on_select_elements.add(function(element_ids) {
var dataset = new openerp.base.DataSetStatic(this, this.field.relation, self.build_context());
var dataset = new openerp.base.DataSetStatic(self, self.field.relation, self.build_context());
dataset.name_get([element_ids[0]], function(data) {
self._change_int_ext_value(data[0]);
pop.stop();
@ -1587,6 +1598,7 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
this.template = "FieldOne2Many";
this.is_started = $.Deferred();
this.form_last_update = $.Deferred();
this.disable_utility_classes = true;
},
start: function() {
this._super.apply(this, arguments);
@ -1632,9 +1644,16 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
controller.on_record_loaded.add_last(function() {
once.resolve();
});
controller.on_pager_action.add_first(function() {
self.save_form_view();
});
controller.$element.find(".oe_form_button_save_edit").hide();
}
self.is_started.resolve();
});
this.viewmanager.on_mode_switch.add_first(function() {
self.save_form_view();
});
setTimeout(function () {
self.viewmanager.start();
}, 0);
@ -1704,6 +1723,9 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
this._super(value);
this.dataset.reset_ids(value);
}
if (this.dataset.index === null && this.dataset.ids.length > 0) {
this.dataset.index = 0;
}
$.when(this.is_started).then(function() {
self.reload_current_view();
});
@ -1728,9 +1750,50 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
this.dataset.to_delete, function(x) {
return commands['delete'](x.id);}));
},
save_form_view: function() {
if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view &&
this.viewmanager.views[this.viewmanager.active_view] &&
this.viewmanager.views[this.viewmanager.active_view].controller) {
var view = this.viewmanager.views[this.viewmanager.active_view].controller;
if (this.viewmanager.active_view === "form") {
var res = view.do_save();
if (res === false) {
// ignore
} else if (res.isRejected()) {
throw "Save or create on one2many dataset is not supposed to fail.";
} else if (!res.isResolved()) {
throw "Asynchronous get_value() is not supported in form view.";
}
return res;
}
}
return false;
},
is_valid: function() {
this.validate();
return this._super();
},
validate: function() {
this.invalid = false;
// TODO niv
var self = this;
var view = self.viewmanager.views[self.viewmanager.active_view].controller;
if (self.viewmanager.active_view === "form") {
for (var f in view.fields) {
f = view.fields[f];
if (!f.is_valid()) {
this.invalid = true;
return;
}
}
}
},
is_dirty: function() {
this.save_form_view();
return this._super();
},
update_dom: function() {
this._super.apply(this, arguments);
this.$element.toggleClass('disabled', this.readonly);
}
});
@ -1797,7 +1860,7 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
var self = this;
this.dataset = new openerp.base.form.Many2ManyDataSet( this, this.field.relation);
this.dataset = new openerp.base.form.Many2ManyDataSet(this, this.field.relation);
this.dataset.m2m = this;
this.dataset.on_unlink.add_last(function(ids) {
self.on_ui_change();
@ -1872,7 +1935,7 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
}
});
openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({
openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({
identifier_prefix: "selectcreatepopup",
template: "SelectCreatePopup",
/**
@ -2012,7 +2075,7 @@ openerp.base.form.SelectCreateListView = openerp.base.ListView.extend({
}
});
openerp.base.form.FormOpenPopup = openerp.base.BaseWidget.extend({
openerp.base.form.FormOpenPopup = openerp.base.OldWidget.extend({
identifier_prefix: "formopenpopup",
template: "FormOpenPopup",
/**

View File

@ -28,7 +28,7 @@ openerp.base.Import = openerp.base.Dialog.extend({
//self.do_import();
}
},
close: function(event, ui){ self.close();}
close: function(event, ui){ self.stop();}
});
},
});

View File

@ -111,7 +111,6 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
this.columns = [];
this.options = _.extend({}, this.defaults, options || {});
this.flags = this.view_manager.flags || {};
this.set_groups(new openerp.base.ListView.Groups(this));
@ -272,6 +271,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id);
this.sidebar.start();
this.sidebar.add_toolbar(data.fields_view.toolbar);
this.set_common_sidebar_sections(this.sidebar);
}
},
/**

View File

@ -1,6 +1,6 @@
openerp.base.search = function(openerp) {
openerp.base.SearchView = openerp.base.Controller.extend({
openerp.base.SearchView = openerp.base.Widget.extend({
init: function(parent, element_id, dataset, view_id, defaults) {
this._super(parent, element_id);
this.view_manager = parent || new openerp.base.NullViewManager();
@ -386,13 +386,13 @@ openerp.base.search.Invalid = openerp.base.Class.extend( /** @lends openerp.base
': [' + this.value + '] is ' + this.message);
}
});
openerp.base.search.Widget = openerp.base.Controller.extend( /** @lends openerp.base.search.Widget# */{
openerp.base.search.Widget = openerp.base.Widget.extend( /** @lends openerp.base.search.Widget# */{
template: null,
/**
* Root class of all search widgets
*
* @constructs
* @extends openerp.base.Controller
* @extends openerp.base.Widget
*
* @param view the ancestor view of this widget
*/
@ -851,7 +851,7 @@ openerp.base.search.ManyToManyField = openerp.base.search.CharField.extend({
// TODO: .related_columns (Array), .context, .domain
});
openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearch = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search',
identifier_prefix: 'extended-search',
init: function (parent, model) {
@ -860,9 +860,7 @@ openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
},
add_group: function() {
var group = new openerp.base.search.ExtendedSearchGroup(this, this.fields);
var render = group.render();
this.$element.find('.searchview_extended_groups_list').append(render);
group.start();
group.appendTo(this.$element.find('.searchview_extended_groups_list'));
this.check_last_element();
},
start: function () {
@ -891,7 +889,7 @@ openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
if(this.$element.closest("table.oe-searchview-render-line").css("display") == "none") {
return null;
}
return _.reduce(this.children,
return _.reduce(this.widget_children,
function(mem, x) { return mem.concat(x.get_domain());}, []);
},
on_activate: function() {
@ -910,14 +908,14 @@ openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
}
},
check_last_element: function() {
_.each(this.children, function(x) {x.set_last_group(false);});
if (this.children.length >= 1) {
this.children[this.children.length - 1].set_last_group(true);
_.each(this.widget_children, function(x) {x.set_last_group(false);});
if (this.widget_children.length >= 1) {
this.widget_children[this.widget_children.length - 1].set_last_group(true);
}
}
});
openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearchGroup = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.group',
identifier_prefix: 'extended-search-group',
init: function (parent, fields) {
@ -926,7 +924,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
},
add_prop: function() {
var prop = new openerp.base.search.ExtendedSearchProposition(this, this.fields);
var render = prop.render({'index': this.children.length - 1});
var render = prop.render({'index': this.widget_children.length - 1});
this.$element.find('.searchview_extended_propositions_list').append(render);
prop.start();
},
@ -943,7 +941,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
});
},
get_domain: function() {
var props = _(this.children).chain().map(function(x) {
var props = _(this.widget_children).chain().map(function(x) {
return x.get_proposition();
}).compact().value();
var choice = this.$element.find(".searchview_extended_group_choice").val();
@ -953,9 +951,9 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
props);
},
stop: function() {
var parent = this.parent;
if (this.parent.children.length == 1)
this.parent.hide();
var parent = this.widget_parent;
if (this.widget_parent.widget_children.length == 1)
this.widget_parent.hide();
this._super();
parent.check_last_element();
},
@ -967,7 +965,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
}
});
openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearchProposition = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition',
identifier_prefix: 'extended-search-proposition',
init: function (parent, fields) {
@ -992,9 +990,12 @@ openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({
});
},
stop: function() {
if (this.parent.children.length == 1)
this.parent.stop();
var parent;
if (this.widget_parent.widget_children.length == 1)
parent = this.widget_parent;
this._super();
if (parent)
parent.stop();
},
changed: function() {
var nval = this.$element.find(".searchview_extended_prop_field").val();
@ -1054,7 +1055,7 @@ openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({
}
});
openerp.base.search.ExtendedSearchProposition.Char = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearchProposition.Char = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.char',
identifier_prefix: 'extended-search-proposition-char',
operators: [
@ -1071,7 +1072,7 @@ openerp.base.search.ExtendedSearchProposition.Char = openerp.base.BaseWidget.ext
return this.$element.val();
}
});
openerp.base.search.ExtendedSearchProposition.DateTime = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearchProposition.DateTime = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.datetime',
identifier_prefix: 'extended-search-proposition-datetime',
operators: [
@ -1093,7 +1094,7 @@ openerp.base.search.ExtendedSearchProposition.DateTime = openerp.base.BaseWidget
});
}
});
openerp.base.search.ExtendedSearchProposition.Date = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearchProposition.Date = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.date',
identifier_prefix: 'extended-search-proposition-date',
operators: [
@ -1115,7 +1116,7 @@ openerp.base.search.ExtendedSearchProposition.Date = openerp.base.BaseWidget.ext
});
}
});
openerp.base.search.ExtendedSearchProposition.Integer = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearchProposition.Integer = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.integer',
identifier_prefix: 'extended-search-proposition-integer',
operators: [
@ -1134,7 +1135,7 @@ openerp.base.search.ExtendedSearchProposition.Integer = openerp.base.BaseWidget.
return Math.round(value);
}
});
openerp.base.search.ExtendedSearchProposition.Float = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearchProposition.Float = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.float',
identifier_prefix: 'extended-search-proposition-float',
operators: [
@ -1153,7 +1154,7 @@ openerp.base.search.ExtendedSearchProposition.Float = openerp.base.BaseWidget.ex
return value;
}
});
openerp.base.search.ExtendedSearchProposition.Selection = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearchProposition.Selection = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.selection',
identifier_prefix: 'extended-search-proposition-selection',
operators: [
@ -1167,7 +1168,7 @@ openerp.base.search.ExtendedSearchProposition.Selection = openerp.base.BaseWidge
return this.$element.val();
}
});
openerp.base.search.ExtendedSearchProposition.Boolean = openerp.base.BaseWidget.extend({
openerp.base.search.ExtendedSearchProposition.Boolean = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.boolean',
identifier_prefix: 'extended-search-proposition-boolean',
operators: [

View File

@ -4,10 +4,10 @@
openerp.base.view_help = function(openerp) {
openerp.base.ProcessView = openerp.base.Controller.extend({
openerp.base.ProcessView = openerp.base.Widget.extend({
});
openerp.base.HelpView = openerp.base.Controller.extend({
openerp.base.HelpView = openerp.base.Widget.extend({
});
};

View File

@ -5,7 +5,7 @@
openerp.base.view_tree = function(openerp) {
openerp.base.views.add('tree', 'openerp.base.TreeView');
openerp.base.TreeView = openerp.base.Controller.extend({
openerp.base.TreeView = openerp.base.Widget.extend({
/**
* Genuine tree view (the one displayed as a tree, not the list)
*/

View File

@ -4,7 +4,7 @@
openerp.base.views = function(openerp) {
openerp.base.ActionManager = openerp.base.Controller.extend({
openerp.base.ActionManager = openerp.base.Widget.extend({
// process all kind of actions
init: function(parent, element_id) {
this._super(parent, element_id);
@ -98,18 +98,21 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
openerp.base.ActionDialog = openerp.base.Dialog.extend({
identifier_prefix: 'action_dialog',
stop: function() {
on_close: function() {
this._super(this, arguments);
if (this.close_callback) {
this.close_callback();
}
},
stop: function() {
this._super(this, arguments);
if (this.viewmanager) {
this.viewmanager.stop();
}
}
});
openerp.base.ViewManager = openerp.base.Controller.extend({
openerp.base.ViewManager = openerp.base.Widget.extend({
init: function(parent, element_id, dataset, views) {
this._super(parent, element_id);
this.model = dataset.model;
@ -126,6 +129,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
* @returns {jQuery.Deferred} initial view loading promise
*/
start: function() {
this._super();
var self = this;
this.dataset.start();
this.$element.html(QWeb.render("ViewManager", {"prefix": this.element_id, views: this.views_src}));
@ -343,7 +347,7 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
}
},
stop: function() {
// should be replaced by automatic destruction implemented in BaseWidget
// should be replaced by automatic destruction implemented in Widget
this._super();
},
/**
@ -370,10 +374,11 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
}
});
openerp.base.Sidebar = openerp.base.Controller.extend({
openerp.base.Sidebar = openerp.base.Widget.extend({
init: function(parent, element_id) {
this._super(parent, element_id);
this.items = {};
this.sections = {};
},
start: function() {
var self = this;
@ -395,11 +400,11 @@ openerp.base.Sidebar = openerp.base.Controller.extend({
classname: 'oe_sidebar_' + type[0]
}
}
self.add_section(type[1], items);
self.add_section(type[0], type[1], items);
}
});
},
add_section: function(name, items) {
add_section: function(code, name, items) {
// For each section, we pass a name/label and optionally an array of items.
// If no items are passed, then the section will be created as a custom section
// returning back an element_id to be used by a custom controller.
@ -408,11 +413,12 @@ openerp.base.Sidebar = openerp.base.Controller.extend({
// label: label to be displayed for the link,
// action: action to be launch when the link is clicked,
// callback: a function to be executed when the link is clicked,
// classname: optionnal dom class name for the line,
// classname: optional dom class name for the line,
// title: optional title for the link
// }
// Note: The item should have one action or/and a callback
var self = this,
section_id = _.uniqueId(this.element_id + '_section_');
section_id = _.uniqueId(this.element_id + '_section_' + code + '_');
if (items) {
for (var i = 0; i < items.length; i++) {
items[i].element_id = _.uniqueId(section_id + '_item_');
@ -422,6 +428,7 @@ openerp.base.Sidebar = openerp.base.Controller.extend({
var $section = $(QWeb.render("Sidebar.section", {
section_id: section_id,
name: name,
classname: 'oe_sidebar_' + code,
items: items
}));
if (items) {
@ -439,6 +446,7 @@ openerp.base.Sidebar = openerp.base.Controller.extend({
});
}
$section.appendTo(this.$element.find('div.sidebar-actions'));
this.sections[code] = $section;
return section_id;
},
do_fold: function() {
@ -452,26 +460,7 @@ openerp.base.Sidebar = openerp.base.Controller.extend({
}
});
openerp.base.Export = openerp.base.Dialog.extend({
dialog_title: "Export",
template: 'ExportDialog',
identifier_prefix: 'export_dialog',
init: function (session, model, domain) {
this._super();
},
start: function () {
this._super();
this.$element.html(this.render());
},
on_button_Export: function() {
console.log("Export")
},
on_button_Cancel: function() {
this.$element.dialog("close");
}
});
openerp.base.View = openerp.base.Controller.extend({
openerp.base.View = openerp.base.Widget.extend({
set_default_options: function(options) {
this.options = options || {};
_.defaults(this.options, {
@ -540,6 +529,60 @@ openerp.base.View = openerp.base.Controller.extend({
*/
set_embedded_view: function(embedded_view) {
this.embedded_view = embedded_view;
},
set_common_sidebar_sections: function(sidebar) {
sidebar.add_section('customize', "Customize", [
{
label: "Manage Views",
callback: this.on_sidebar_manage_view,
title: "Manage views of the current object"
}, {
label: "Edit Workflow",
callback: this.on_sidebar_edit_workflow,
title: "Manage views of the current object",
classname: 'oe_hide oe_sidebar_edit_workflow'
}, {
label: "Customize Object",
callback: this.on_sidebar_customize_object,
title: "Manage views of the current object"
}
]);
sidebar.add_section('other', "Other Options", [
{
label: "Import",
callback: this.on_sidebar_import
}, {
label: "Export",
callback: this.on_sidebar_export
}, {
label: "Translate",
callback: this.on_sidebar_translate,
classname: 'oe_hide oe_sidebar_translate'
}, {
label: "View Log",
callback: this.on_sidebar_view_log,
classname: 'oe_hide oe_sidebar_view_log'
}
]);
},
on_sidebar_manage_view: function() {
console.log('Todo');
},
on_sidebar_edit_workflow: function() {
console.log('Todo');
},
on_sidebar_customize_object: function() {
console.log('Todo');
},
on_sidebar_import: function() {
},
on_sidebar_export: function() {
var export_view = new openerp.base.DataExport(this, this.dataset);
export_view.start(false);
},
on_sidebar_translate: function() {
},
on_sidebar_view_log: function() {
}
});

View File

@ -19,23 +19,31 @@
</div>
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%" class="main_table">
<tr>
<td colspan="2">
<td colspan="2" valign="top">
<div id="oe_header" class="header"></div>
<div id="oe_menu" class="menu"></div>
</td>
</tr>
<tr>
<td valign="top" id="oe_secondary_menu" class="secondary_menu">
<td valign="top" class="login-container" colspan="2">
<div id="oe_login" class="login"></div>
</td>
<td valign="top" >
<div id="oe_app" class="oe-application">
<div style="width:100%;">&amp;nbsp;</div>
</div>
</tr>
<tr class="db_options_row">
<td valign="top" class="db_container">
<div id="oe_database" class="database"></div>
</td>
<td valign="top">
<div id="oe_db_options"></div>
</td>
</tr>
<tr>
<td valign="top" class="login-container" colspan="2">
<div id="oe_login" class="login"></div>
<td valign="top" id="oe_secondary_menu" class="secondary_menu">
</td>
<td valign="top">
<div id="oe_app" class="oe-application">
<div style="width: 100%;">&amp;nbsp;</div>
</div>
</td>
</tr>
<tr>
@ -50,6 +58,188 @@
<t t-name="Loading">
Loading...
</t>
<t t-name="Database">
<ul class="db_options" style="padding: 0px; display: inline;">
<li id="db-create">Create</li>
<li id="db-drop">Drop</li>
<li id="db-backup">Backup</li>
<li id="db-restore">Restore</li>
<li id="db-change-password">Password</li>
<li id="back-to-login">Back to Login</li>
</ul>
</t>
<t t-name="CreateDB">
<form name="create_db_form" method="POST">
<table width="100%">
<tr>
<td class="option_string">
CREATE DATABASE
</td>
</tr>
</table>
<table align="center" class="db_option_table">
<tr>
<td><label for="super_admin_pwd">Super admin password:</label></td>
<td><input type="password" name="super_admin_pwd"
class="required" autofocus="autofocus"/></td>
</tr>
<tr>
<td><label for="db_name">New database name:</label></td>
<td><input type="text" name="db_name" class="required"/></td>
</tr>
<tr>
<td><label for="demo_data">Load Demonstration data:</label></td>
<td><input type="checkbox" name="demo_data" checked='checked'/></td>
</tr>
<tr>
<td><label for="db_lang">Default language:</label></td>
<td>
<select name="db_lang" t-if="lang_list">
<t t-foreach="lang_list" t-as="lang">
<option t-att-value="lang[0]"
t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined">
<t t-esc="lang[1]"/></option>
</t>
</select>
</td>
</tr>
<tr>
<td><label for="create_admin_pwd">Super admin password:</label></td>
<td><input type="password" name="create_admin_pwd" class="required"/></td>
</tr>
<tr>
<td><label for="create_confirm_pwd">Confirm password:</label></td>
<td><input type="password" name="create_confirm_pwd" class="required"
equalTo="input[name=create_admin_pwd]"/></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="Create"/></td>
</tr>
</table>
</form>
</t>
<t t-name="DropDB">
<form name="drop_db_form" method="POST">
<table width="100%">
<tr>
<td class="option_string">
DROP DATABASE
</td>
</tr>
</table>
<table align="center" class="db_option_table">
<tr>
<td><label for="drop_db">Database:</label></td>
<td>
<select t-if="db_list" name="drop_db" autofocus="autofocus">
<t t-foreach="db_list" t-as="db">
<option t-att-value="db"><t t-esc="db"/></option>
</t>
</select>
</td>
</tr>
<tr>
<td><label for="drop_password">Password:</label></td>
<td><input type="password" name="drop_pwd" class="required"/></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="Drop"/></td>
</tr>
</table>
</form>
</t>
<t t-name="BackupDB">
<form name="backup_db_form" method="POST" target="backup-target"
action="/base/database/backup">
<input type="hidden" name="token"/>
<table width="100%">
<tr>
<td class="option_string">
BACKUP DATABASE
</td>
</tr>
</table>
<table align="center" class="db_option_table">
<tr>
<td><label for="backup_db">Database:</label></td>
<td>
<select t-if="db_list" name="backup_db" autofocus="autofocus">
<t t-foreach="db_list" t-as="db">
<option t-att-value="db"><t t-esc="db"/></option>
</t>
</select>
</td>
</tr>
<tr>
<td><label for="backup_pwd">Password:</label></td>
<td><input type="password" name="backup_pwd" class="required"/></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="Backup"/></td>
</tr>
</table>
</form>
</t>
<t t-name="RestoreDB">
<form name="restore_db_form" method="POST">
<table width="100%">
<tr>
<td class="option_string">
RESTORE DATABASE
</td>
</tr>
</table>
<table align="center" class="db_option_table">
<tr>
<td><label for="restore_db">File:</label></td>
<td><input type="file" name="db_file" class="required"
autofocus="autofocus"/></td>
</tr>
<tr>
<td><label for="restore_pwd">Password:</label></td>
<td><input type="password" name="restore_pwd" class="required"/></td>
</tr>
<tr>
<td><label for="new_db">New database name:</label></td>
<td><input type="text" name="new_db" class="required"/></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="Restore"/></td>
</tr>
</table>
</form>
</t>
<t t-name="Change_DB_Pwd">
<form name="change_pwd_form" method="POST">
<table width="100%">
<tr>
<td class="option_string">
CHANGE DATABASE PASSWORD
</td>
</tr>
</table>
<table align="center" class="db_option_table">
<tr>
<td><label for="old_pwd">Old password:</label></td>
<td><input type="password" name="old_pwd" class="required"
minlength="1" autofocus="autofocus"/></td>
</tr>
<tr>
<td><label for="new_pwd">New password:</label></td>
<td><input type="password" name="new_pwd" class="required"
minlength="1"/></td>
</tr>
<tr>
<td><label for="confirm_pwd">Confirm password:</label></td>
<td><input type="password" name="confirm_pwd" class="required"
equalTo="input[name=new_pwd]" minlength="1"/></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="Change Password"/></td>
</tr>
</table>
</form>
</t>
<t t-name="Login">
<form>
<fieldset>
@ -92,6 +282,7 @@
<tr>
<td></td>
<td>
<button type="button" id="oe-db-config">Database</button>
<button type="submit" name="submit">Login</button>
</td>
</tr>
@ -248,10 +439,10 @@
</t>
<t t-name="Sidebar.section">
<h2><t t-esc="name"/></h2>
<div t-att-id="section_id">
<div t-att-id="section_id" t-att-class="classname">
<ul t-if="items">
<li t-foreach="items" t-as="item" t-att-class="item.classname">
<a class="oe_sidebar_action_a" t-att-id="item.element_id" href="#">
<a class="oe_sidebar_action_a" t-att-id="item.element_id" t-att-title="item.title" href="#">
<t t-esc="item.label"/>
</a>
</li>
@ -261,11 +452,11 @@
<table t-name="ListView" class="oe-listview-content">
<t t-set="columns_count" t-value="visible_columns.length + (options.selectable ? 1 : 0) + (options.deletable ? 1 : 0)"/>
<thead class="ui-widget-header">
<tr t-if="flags.action_buttons !== false or flags.pager !== false">
<tr t-if="options.action_buttons !== false or options.pager !== false">
<th t-att-colspan="columns_count">
<table>
<tr>
<td t-if="flags.action_buttons !== false" class="oe-actions">
<td t-if="options.action_buttons !== false" class="oe-actions">
<button type="button" class="oe-list-add"
t-if="options.addable">
<t t-esc="options.addable"/>
@ -275,7 +466,7 @@
Delete
</button>
</td>
<th t-if="flags.pager !== false" class="oe-list-pager">
<th t-if="options.pager !== false" class="oe-list-pager">
<button type="button" disabled="disabled"
data-pager-action="first">First</button>
<button type="button" disabled="disabled"
@ -357,7 +548,7 @@
<t t-name="FormView">
<div class="oe_form_header" t-att-id="view.element_id + '_header'">
<h2 class="oe_view_title"><t t-esc="view.fields_view.arch.attrs.string"/> <button class="oe_get_xml_view">xml</button></h2>
<div class="oe_form_buttons" t-if="view.flags.action_buttons !== false">
<div class="oe_form_buttons" t-if="view.options.action_buttons !== false">
<!--<button type="button" class="oe_form_button_save">
<span class="oe_form_on_update">Save</span>
<span class="oe_form_on_create">Create</span>
@ -369,7 +560,7 @@
<!--<button type="button" class="oe_form_button_cancel">Cancel</button>-->
<button type="button" class="oe_form_button_new">New</button>
</div>
<div class="oe_form_pager" t-if="view.flags.pager !== false">
<div class="oe_form_pager" t-if="view.options.pager !== false">
<button type="button" data-pager-action="first">First</button>
<button type="button" data-pager-action="previous">&lt;&lt;</button>
@ -944,6 +1135,147 @@
.unwrap();
</t>
</t>
<t t-name="ExportView">
<a id="exportview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Export</a>
</t>
<t t-name="ExportTreeView">
<table class="view" style="background-color: #F3F3F3;">
<tr>
<td align="left">
This wizard will export all data that matches the current search criteria to a CSV file.
You can export all data or only the fields that can be reimported after modification.
</td>
</tr>
<tr>
<td>
<table>
<tr>
<td class="label"><label>Export Type:</label></td>
<td>
<select id="import_compat" name="import_compat">
<option value="1">Import Compatible Export</option>
<option value="0">Export all Data</option>
</select>
</td>
<td class="label"><label>Export Format</label></td>
<td>
<select id="export_format" name="export_format">
<option value="csv">CSV</option>
<option value="xls">Excel</option>
</select>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table class="oe_export_fields_selector_export">
<tr>
<th class="oe_view_title" valign="bottom">Available fields</th>
<th class="oe_view_title"></th>
<th class="oe_view_title">Fields to export
<a style="color: blue; text-decoration: none;" href="#" id="export_new_list">Save fields list</a>
<div id="savenewlist"></div>
<div id="ExistsExportList"></div>
</th>
</tr>
<tr>
<td class="oe_export_fields_selector_left">
<div id="left_field_panel">
</div>
</td>
<td>
<table class="oe_export_fields_selector_center">
<tr>
<td align="center">
<button id="add_field" class="oe_export_button_export">Add</button>
</td>
</tr>
<tr>
<td align="center">
<button id="remove_field" class="oe_export_button_export">Remove</button>
</td>
</tr>
<tr>
<td align="center">
<button id="remove_all_field" class="oe_export_button_export">Remove All</button>
</td>
</tr>
</table>
</td>
<td class="oe_export_fields_selector_right">
<select name="fields_list" id="fields_list" multiple="multiple"></select>
</td>
</tr>
</table>
</td>
</tr>
</table>
</t>
<t t-name="ExportTreeView-Secondary">
<table id="field-tree-structure" class="oe_export_fields_selector_export" cellspacing="0" cellpadding="0">
<tr><th class="oe_export_tree_header"> Name </th></tr>
<t t-call="ExportTreeView-Secondary.children"/>
</table>
</t>
<t t-name="ExportTreeView-Secondary.children">
<t t-foreach="fields" t-as="field" >
<tr t-att-id="'treerow-' + field.id" class="oe_export_row">
<td>
<table class="tree_grid" border="0">
<tr class="oe_export_row">
<t t-foreach="(field.id).split('/')" t-as="level" >
<t t-if="(field.id).split('/')[0] != level">
<td width="18">&amp;nbsp;</td>
</t>
</t>
<td valign="top" align="left" style="cursor: pointer;" width="18">
<t t-if="(field.children).length >= 1">
<t t-if="(field.id).split('/').length != 3">
<img t-att-id="'parentimg-' + field.id" src="/base/static/src/img/expand.gif" width="16" height="16" border="0"/>
</t>
</t>
</td>
<td id="tree-column" valign="middle" align="left" style="cursor: pointer;">
<a t-att-id="'export-' + field.id" t-att-string="field.string" href="javascript: void(0);" style="text-decoration: none;">
<t t-esc="field.string"/>
</a>
</td>
</tr>
</table>
</td>
</tr>
</t>
</t>
<t t-name="ExportNewList">
<tr>
<th><label>Save as:</label></th>
<td><input size="10" type="text" id="savelist_name"/></td>
<td><button class="oe_export_button_export" id="add_export_list">Ok</button></td>
</tr>
</t>
<t t-name="Exists.ExportList">
<tr><th align="right"><label >Saved exports:</label></th></tr>
<tr align="left">
<td>
<select id="saved_export_list" style="width: 100%;">
<option></option>
<t t-foreach="existing_exports" t-as="export">
<option t-att-value="export.id"><t t-esc="export.name"/></option>
</t>
</select>
</td>
<td><button class="oe_export_button_export" id="delete_export_list">Delete</button></td>
</tr>
</t>
<t t-name="ImportView">
<a id="importview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Import</a>
</t>

View File

@ -18,15 +18,6 @@ $(document).ready(function () {
}
}};
var fuck_that_shit = {
action: {
flags: {}
},
sidebar: {
set_toolbar: function () {}
}
};
var openerp;
module("ListView", {
setup: function () {
@ -42,8 +33,7 @@ $(document).ready(function () {
test('render selection checkboxes', 2, function () {
var listview = new openerp.base.ListView(
fuck_that_shit, null,
'qunit-fixture', {model: null, ids: [null, null, null], index: 0});
null, 'qunit-fixture', {model: null, ids: [null, null, null], index: 0});
listview.on_loaded(fvg);
@ -60,8 +50,7 @@ $(document).ready(function () {
});
test('render no checkbox if selectable=false', 1, function () {
var listview = new openerp.base.ListView(
fuck_that_shit, null,
'qunit-fixture', {model: null, ids: [null, null, null], index: 0}, false,
null, 'qunit-fixture', {model: null, ids: [null, null, null], index: 0}, false,
{selectable: false});
listview.on_loaded(fvg);
@ -75,8 +64,7 @@ $(document).ready(function () {
});
test('select a bunch of records', 2, function () {
var listview = new openerp.base.ListView(
fuck_that_shit, null, 'qunit-fixture',
{model: null, ids: [1, 2, 3], index: 0});
null, 'qunit-fixture', {model: null, ids: [1, 2, 3], index: 0});
listview.on_loaded(fvg);
listview.do_fill_table({records: [
@ -94,8 +82,7 @@ $(document).ready(function () {
});
test('render deletion button if list is deletable', 1, function () {
var listview = new openerp.base.ListView(
fuck_that_shit, null, 'qunit-fixture',
{model: null, ids: [null, null, null], index: 0});
null, 'qunit-fixture', {model: null, ids: [null, null, null], index: 0});
listview.on_loaded(fvg);
@ -112,7 +99,7 @@ $(document).ready(function () {
2, function () {
var deleted;
var listview = new openerp.base.ListView(
fuck_that_shit, null, 'qunit-fixture',
null, 'qunit-fixture',
{model: null, unlink: function (ids) {
deleted = ids;
}, ids: [1, 2, 3], index: 0});
@ -132,7 +119,7 @@ $(document).ready(function () {
test('multiple records deletion', 1, function () {
var deleted;
var listview = new openerp.base.ListView(
fuck_that_shit, null, 'qunit-fixture',
null, 'qunit-fixture',
{model: null, unlink: function (ids) {
deleted = ids;
}, ids: [1, 2, 3], index: 0});

View File

@ -1,56 +1,14 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
"name" : "Basic Calendar Functionality",
"version" : "1.0",
"depends" : ["base"],
'complexity': "easy",
'description': """
This is a full-featured calendar system.
========================================
It supports:
- Calendar of events
- Alerts (create requests)
- Recurring events
- Invitations to people""",
"author" : "OpenERP SA",
'category': 'Tools',
'website': 'http://www.openerp.com',
"init_xml" : [
'base_calendar_data.xml'
"name": "Base calendar",
"version": "2.0",
"depends": ['base'],
"js": [
'static/lib/dhtmlxScheduler/codebase/dhtmlxscheduler.js',
'static/lib/dhtmlxScheduler/codebase/ext/dhtmlxscheduler_minical.js',
'static/src/js/calendar.js'
],
"demo_xml" : [],
"update_xml" : [
'security/calendar_security.xml',
'security/ir.model.access.csv',
'wizard/base_calendar_invite_attendee_view.xml',
'base_calendar_view.xml'
],
"test" : ['test/base_calendar_test.yml'],
"installable" : True,
"active" : False,
"certificate" : "00694071962960352821",
'images': ['images/base_calendar1.jpeg','images/base_calendar2.jpeg','images/base_calendar3.jpeg','images/base_calendar4.jpeg',],
"css": ['static/lib/dhtmlxScheduler/codebase/dhtmlxscheduler.css',
'static/lib/dhtmlxScheduler/codebase/ext/dhtmlxscheduler_ext.css'
],
'active': True
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,356 +1,10 @@
from base.controllers.main import View
import openerpweb, time, math, re, datetime as DT, pytz
COLOR_PALETTE = ['#f57900', '#cc0000', '#d400a8', '#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400',
'#fcaf3e', '#ef2929', '#ff00c9', '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f',
'#ff8e00', '#ff0000', '#b0008c', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
'#905000', '#9b0000', '#840067', '#510090', '#0000c9', '#009b00', '#9abe00', '#ffc900', ]
_colorline = ['#%02x%02x%02x' % (25 + ((r + 10) % 11) * 23, 5 + ((g + 1) % 11) * 20, 25 + ((b + 4) % 11) * 23) for r in range(11) for g in range(11) for b in range(11) ]
DT_SERVER_FORMATS = {
'datetime' : '%Y-%m-%d %H:%M:%S',
'date' : '%Y-%m-%d',
'time' : '%H:%M:%S'
}
DT_FORMAT_INFO = {'datetime' : ('%Y-%m-%d %H:%M:%S', DT.datetime, 0, 6),
'date': ('%Y-%m-%d', DT.date, 0, 3),
'time': ('%H:%M:%S', DT.time, 3, 6)}
def choice_colors(n):
if n > len(COLOR_PALETTE):
return _colorline[0:-1:len(_colorline) / (n + 1)]
elif n:
return COLOR_PALETTE[:n]
return []
import openerpweb
class CalendarView(View):
_cp_path = "/base_calendar/calendarview"
mode = 'month'
date_start = None
date_delay = None
date_stop = None
color_field = None
day_length = 8
use_search = False
selected_day = None
date_format = '%Y-%m-%d'
info_fields = []
fields = {}
events = []
colors = {}
color_values = []
remote_timezone = 'utc'
client_timezone = False
calendar_fields = {}
concurrency_info = None
ids = []
model = ''
domain = []
context = {}
@openerpweb.jsonrequest
def load(self, req, model, view_id):
fields_view = self.fields_view_get(req, model, view_id, 'calendar')
return {'fields_view':fields_view}
def convert(self, event):
fields = [self.date_start]
if self.date_stop:
fields.append(self.date_stop)
for fld in fields:
fld_type = self.fields[fld]['type']
fmt = DT_SERVER_FORMATS[fld_type]
if event[fld] and fmt:
event[fld] = time.strptime(event[fld], fmt)
# default start/stop time is 9:00 AM / 5:00 PM
if fld_type == 'date' and event[fld]:
ds = list(event[fld])
if fld == self.date_start:
ds[3] = 9
elif fld == self.date_stop:
ds[3] = 17
event[fld] = tuple(ds)
@openerpweb.jsonrequest
def schedule_events(self, req, **kw):
self.model = kw['model']
self.mode = kw.get('mode') or self.mode or 'month'
self.fields = kw['fields']
self.color_field = kw.get('color_field') or self.color_field or None
self.colors = kw.get('colors') or {}
self.calendar_fields = kw['calendar_fields']
self.info_fields = kw['info_fields']
self.date_start = self.calendar_fields['date_start']['name']
self.domain = kw.get('domain') or []
self.remote_timezone = req.session.remote_timezone
self.client_timezone = req.session.client_timezone
if self.calendar_fields.get('date_stop'):
self.date_stop = self.calendar_fields['date_stop']['name']
if self.calendar_fields.get('date_delay'):
self.date_delay = self.calendar_fields['date_delay']['name']
model = req.session.model(self.model)
event_ids = model.search(self.domain)
self.events = model.read(event_ids, self.fields.keys())
result = []
self.date_format = req.session._lang and req.session._lang['date_format']
if self.color_field:
for evt in self.events:
key = evt[self.color_field]
name = key
value = key
if isinstance(key, list): # M2O, XMLRPC returns List instead of Tuple
name = key[0]
value = key[-1]
evt[self.color_field] = key = key[-1]
if isinstance(key, tuple): # M2O
value, name = key
self.colors[key] = (name, value, None)
colors = choice_colors(len(self.colors))
for i, (key, value) in enumerate(self.colors.items()):
self.colors[key] = [value[0], value[1], colors[i]]
for evt in self.events:
self.convert(evt)
a = self.get_event_widget(evt)
result.append(a)
return {'result':result,'sidebar':self.colors}
def parsedatetime(self, string):
kind = 'datetime'
if '-' in string and ':' in string:
kind = 'datetime'
elif '-' in string:
kind = 'date'
elif ':' in string:
kind = 'time'
fmt, obj, i, j = DT_FORMAT_INFO[kind]
return obj(*time.strptime(string, fmt)[i:j])
def parse_datetime(self, value, kind="datetime", as_timetuple=False):
server_format = DT_SERVER_FORMATS[kind]
local_format = self.date_format
if not value:
return False
if isinstance(value, (time.struct_time, tuple)):
value = time.strftime(local_format, value)
try:
value = time.strptime(value, local_format)
except ValueError:
try:
# might be in server format already (e.g. filter domain)
value = time.strptime(value, server_format)
except ValueError:
try:
dt = list(time.localtime())
dt[2] = int(value)
value = tuple(dt)
except:
return False
if kind == "datetime":
try:
value = self.tz_convert(value, 'parse')
except Exception,e:
print "*******************Error in timezone parsing *********",e
if as_timetuple:
return value
return time.strftime(server_format, value)
@openerpweb.jsonrequest
def edit_events(self, req,**kw):
data = {}
ds = self.parsedatetime(kw['start_date'])
de = self.parsedatetime(kw['end_date'])
data[kw['calendar_fields']['date_start']['name']] = self.parse_datetime(ds.timetuple())
if 'date_stop' in kw['calendar_fields']:
data[kw['calendar_fields']['date_stop']['name']] = self.parse_datetime(de.timetuple())
elif 'date_delay' in kw['calendar_fields']:
day_length = kw['calendar_fields']['day_length']
tds = time.mktime(ds.timetuple())
tde = time.mktime(de.timetuple())
n = (tde - tds) / (60 * 60)
if n > day_length:
d = math.floor(n / 24)
h = n % 24
n = d * day_length + h
data[kw['calendar_fields']['date_delay']['name']] = n
error = None
try:
req.session.model(kw['model']).write([int(kw['id'])], data)
except Exception, e:
error = e
return error
def tz_convert(self, struct_time, action):
# if no client timezone is configured, consider the client is in the same
# timezone as the server
lzone = pytz.timezone(self.client_timezone or self.remote_timezone)
szone = pytz.timezone(self.remote_timezone)
dt = DT.datetime.fromtimestamp(time.mktime(struct_time))
if action == 'parse':
fromzone = lzone
tozone = szone
elif action == 'format':
fromzone = szone
tozone = lzone
else:
raise Exception("_tz_convert action should be 'parse' or 'format'. Not '%s'" % (action, ))
localized_original_datetime = fromzone.localize(dt, is_dst=True)
destination_datetime = localized_original_datetime.astimezone(tozone)
return destination_datetime.timetuple()
def format_datetime(self, value, kind="datetime", as_timetuple=False):
"""Convert date value to the local datetime considering timezone info.
@param value: the date value
@param kind: type of the date value (date, time or datetime)
@param as_timetuple: return timetuple
@type value: basestring or time.time_tuple)
@return: string or timetuple
"""
server_format = DT_SERVER_FORMATS[kind]
local_format = self.date_format
if not value:
return ''
if isinstance(value, (time.struct_time, tuple)):
value = time.strftime(server_format, value)
if isinstance(value, DT.datetime):
value = value
try:
value = DT.datetime.strptime(value[:10], server_format)
return value.strftime(local_format)
except:
return ''
value = value.strip()
# remove trailing miliseconds
value = re.sub("(.*?)(\s+\d{2}:\d{2}:\d{2})(\.\d+)?$", "\g<1>\g<2>", value)
# add time part in value if missing
if kind == 'datetime' and not re.search('\s+\d{2}:\d{2}:\d{2}?$', value):
value += ' 00:00:00'
# remove time part from value
elif kind == 'date':
value = re.sub('\s+\d{2}:\d{2}:\d{2}(\.\d+)?$', '', value)
value = time.strptime(value, server_format)
if kind == "datetime":
try:
value = self.tz_convert(value, 'format')
except Exception, e:
print "\n\n\n************ Error in timezone formatting", e
if as_timetuple:
return value
return time.strftime(local_format, value)
def get_event_widget(self, event):
title = '' # the title
description = [] # the description
if self.info_fields:
f = self.info_fields[0]
s = event[f]
if isinstance(s, (tuple, list)): s = s[-1]
title = s
for f in self.info_fields[1:]:
s = event[f]
if isinstance(s, (tuple, list)):
s = s[-1]
if s:
description.append(str(s))
starts = event.get(self.date_start)
ends = event.get(self.date_delay) or 1.0
span = 0
if starts and ends:
n = 0
h = ends
if ends == self.day_length:
span = 1
elif ends > self.day_length:
n = ends / self.day_length
h = ends % self.day_length
n = int(math.floor(n))
if h > 0:
span = n + 1
else:
span = n
ends = time.localtime(time.mktime(starts) + (h * 60 * 60) + (n * 24 * 60 * 60))
if starts and self.date_stop:
ends = event.get(self.date_stop)
if not ends:
ends = time.localtime(time.mktime(starts) + 60 * 60)
tds = time.mktime(starts)
tde = time.mktime(ends)
if tds >= tde:
tde = tds + 60 * 60
ends = time.localtime(tde)
n = (tde - tds) / (60 * 60)
if n >= self.day_length:
span = math.ceil(n / 24)
starts = self.format_datetime(starts, "datetime", True)
ends = self.format_datetime(ends, "datetime", True)
title = title.strip()
description = ', '.join(description).strip()
return {'id': event['id'], 'start_date': str(DT.datetime(*starts[:6])), 'end_date': str(DT.datetime(*ends[:6])), 'text': title, 'title': description, 'color': self.colors[event[self.color_field]][-1]}
def load(self, req, model, view_id, toolbar=False):
fields_view = self.fields_view_get(req, model, view_id, 'calendar', toolbar=toolbar)
return {'fields_view': fields_view}

View File

@ -7,218 +7,381 @@ QWeb.add_template('/base_calendar/static/src/xml/base_calendar.xml');
openerp.base.views.add('calendar', 'openerp.base_calendar.CalendarView');
openerp.base_calendar.CalendarView = openerp.base.View.extend({
// Dhtmlx scheduler ?
init: function(parent, element_id, dataset, view_id) {
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.dataset_index = 0;
this.model = dataset.model;
this.view_id = view_id;
this.fields_view = {};
this.widgets = {};
this.widgets_counter = 0;
this.fields = this.dataset.fields ? this.dataset.fields: {};
this.datarecord = {};
this.name = "";
this.date_start = "";
this.date_delay = "";
this.date_stop = "";
this.color_field = "";
this.day_lenth = 8;
this.colors = [];
this.color_values = [];
this.calendar_fields = {};
this.info_fields = [];
this.domain = this.dataset._domain ? this.dataset._domain: [];
this.context = {};
},
start: function() {
this.rpc("/base_calendar/calendarview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
},
on_loaded: function(result) {
var self = this;
var params = {};
this.fields_view = result.fields_view;
this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
this.view_id = this.fields_view.view_id;
this.date_start = this.fields_view.arch.attrs.date_start;
this.date_delay = this.fields_view.arch.attrs.date_delay;
this.date_stop = this.fields_view.arch.attrs.date_stop;
this.colors = this.fields_view.arch.attrs.colors;
this.day_length = this.fields_view.arch.attrs.day_length || 8;
this.color_field = this.fields_view.arch.attrs.color;
this.fields = this.fields_view.fields;
//* Calendar Fields *
this.calendar_fields['date_start'] = {'name': this.date_start, 'kind': this.fields[this.date_start]['type']};
if(this.date_delay)
this.calendar_fields['date_delay'] = {'name': this.date_delay, 'kind': this.fields[this.date_delay]['type']};
if(this.date_stop)
this.calendar_fields['date_stop'] = {'name': this.date_stop, 'kind': this.fields[this.date_stop]['type']};
this.calendar_fields['day_length'] = this.day_length;
//* ------- *
for(var fld=0;fld<this.fields_view.arch.children.length;fld++) {
this.info_fields.push(this.fields_view.arch.children[fld].attrs.name);
}
this.load_scheduler();
},
load_scheduler:function() {
var self = this;
var params = {};
params['model'] = this.model;
params['calendar_fields'] = this.calendar_fields;
params['info_fields'] = this.info_fields;
params['fields'] = this.fields;
params['color_field'] = this.color_field;
params['domain'] = this.domain;
params['colors'] = this.colors;
/*
* Start dhtmlx Schedular
*/
scheduler.clearAll();
scheduler.config.xml_date="%Y-%m-%d %H:%i";
scheduler.config.multi_day = true; //Multi day events are not rendered in daily and weekly views
this.rpc(
'/base_calendar/calendarview/schedule_events',
params,
function(result) {
self.schedule_events(result);
}
)
},
schedule_events: function(result) {
var self = this;
var res = result.result;
var sidebar = result.sidebar;
this.$element.html(QWeb.render("CalendarView", {"view": this, "fields_view": this.fields_view, "sidebar":sidebar, "calendar":this}));
// Initialize Sceduler
scheduler.init('openerp_scheduler',null,"month");
scheduler.parse(res,"json");
jQuery('#dhx_minical_icon').bind('click', this.mini_calendar);
// To Change Event
scheduler.attachEvent(
'onEventChanged'
,function(event_id, event_object) {
self.edit_event(event_id, event_object)
});
/*
* Create Sidebar
*/
jQuery('#calendar-sidebar').append(
jQuery('<table>',{'width':'100%','cellspacing': 0, 'cellpadding': 0, 'id':'cal-sidebar-option'})
)
for(s in sidebar) {
jQuery('#cal-sidebar-option').append(
jQuery('<tr>').append(
jQuery('<td>').append(
jQuery('<div>')
.append(
jQuery('<input>',
{
'type': 'checkbox',
'id':sidebar[s][0],
'value':sidebar[s][0]
}).bind('click',function(){
self.reload_scheduler()
}),
sidebar[s][1]
)
.css('background-color',sidebar[s][sidebar[s].length-1])
)
)
)
}
},
convert_date_format: function(start_date, end_date) {
var params = {};
params['start_date'] = start_date.getFullYear() +'-' + start_date.getMonth()+'-' + start_date.getDate()+' '+start_date.getHours()+':'+start_date.getMinutes()+':'+start_date.getSeconds();
if(end_date) {
params['end_date'] = end_date.getFullYear() +'-' + end_date.getMonth()+'-' + end_date.getDate()+' '+end_date.getHours()+':'+end_date.getMinutes()+':'+end_date.getSeconds();
}
return params;
},
edit_event: function(evt_id, evt_object) {
var dates = this.convert_date_format(evt_object.start_date, evt_object.end_date);
this.rpc(
'/base_calendar/calendarview/edit_events',
{
'start_date': dates.start_date,
'end_date': dates.end_date,
'id': evt_id,
'model': this.model,
'info_fields': this.info_fields,
'fields': this.fields,
'calendar_fields': this.calendar_fields
}
);
},
mini_calendar: function() {
if(scheduler.isCalendarVisible()) {
scheduler.destroyCalendar();
} else {
scheduler.renderCalendar({
position:"dhx_minical_icon",
date:scheduler._date,
navigation:true,
handler:function(date,calendar){
scheduler.setCurrentView(date);
scheduler.destroyCalendar()
}
});
}
},
reload_scheduler: function() {
// self.color_field
console.log('Reload Scheduler>>>')
},
do_search: function (domains, contexts, groupbys) {
this.notification.notify("Searching caldendar");
this.domain = this.dataset.domain || [];
this.context = this.dataset.context || {};
this.has_been_loaded = $.Deferred();
this.options = options || {};
},
do_show: function () {
this.$element.show();
start: function() {
this.rpc("/base_calendar/calendarview/load", {"model": this.model, "view_id": this.view_id, 'toolbar': true}, this.on_loaded);
},
on_loaded: function(data) {
this.calendar_fields = {};
this.ids = this.dataset.ids;
this.color_values = [];
this.info_fields = [];
this.fields_view = data.fields_view;
this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
this.view_id = this.fields_view.view_id;
this.date_start = this.fields_view.arch.attrs.date_start;
this.date_delay = this.fields_view.arch.attrs.date_delay;
this.date_stop = this.fields_view.arch.attrs.date_stop;
this.colors = this.fields_view.arch.attrs.colors;
this.day_length = this.fields_view.arch.attrs.day_length || 8;
this.color_field = this.fields_view.arch.attrs.color;
this.fields = this.fields_view.fields;
//* Calendar Fields *
this.calendar_fields['date_start'] = {'name': this.date_start, 'kind': this.fields[this.date_start]['type']};
if (this.date_delay) {
this.calendar_fields['date_delay'] = {'name': this.date_delay, 'kind': this.fields[this.date_delay]['type']};
}
if (this.date_stop) {
this.calendar_fields['date_stop'] = {'name': this.date_stop, 'kind': this.fields[this.date_stop]['type']};
}
for (var fld = 0; fld < this.fields_view.arch.children.length; fld++) {
this.info_fields.push(this.fields_view.arch.children[fld].attrs.name);
}
this.$element.html(QWeb.render("CalendarView", {"fields_view": this.fields_view}));
if (this.options.sidebar && this.options.sidebar_id) {
this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id);
this.sidebar.start();
this.sidebar.navigator = new openerp.base_calendar.SidebarNavigator(this.sidebar, this.sidebar.add_section('navigator', "Navigator"), this);
this.sidebar.responsible = new openerp.base_calendar.SidebarResponsible(this.sidebar, this.sidebar.add_section('responsible', "Responsible"), this);
this.sidebar.add_toolbar(data.fields_view.toolbar);
this.set_common_sidebar_sections(this.sidebar);
this.sidebar.do_unfold();
this.sidebar.do_fold.add_last(this.resize_scheduler);
this.sidebar.do_unfold.add_last(this.resize_scheduler);
this.sidebar.do_toggle.add_last(this.resize_scheduler);
}
this.init_scheduler();
this.load_scheduler();
this.has_been_loaded.resolve();
},
init_scheduler: function() {
var self = this;
scheduler.config.api_date = "%Y-%m-%d %H:%M:%S";
scheduler.config.details_on_dblclick = true;
scheduler.config.details_on_create = true;
if (this.fields[this.date_start]['type'] == 'time') {
scheduler.config.xml_date = "%H:%M:%S";
} else {
scheduler.config.xml_date = "%Y-%m-%d %H:%M:%S";
}
this.mode = this.mode || 'month';
scheduler.config.multi_day = true; //Multi day events are not rendered in daily and weekly views
// Initialize Sceduler
scheduler.init('openerp_scheduler', null, this.mode);
// Event Options Click,edit
scheduler.attachEvent('onDblClick', this.popup_event);
scheduler.attachEvent('onEventCreated', function(event_id, e) {
//Replace default Lightbox with Popup Form of new Event
scheduler.showLightbox = function() {
//Delete Newly created Event,Later we reload Scheduler
scheduler.deleteEvent(event_id);
self.popup_event();
}
});
scheduler.attachEvent('onBeforeEventChanged', function(event_obj, native_event, is_new) {
var is_event_exist = $.inArray(event_obj.id, self.dataset.ids);
if (is_event_exist >= 0 || !is_new) {
// try to save Event.
var data = {};
self.mode = scheduler._mode;
var date_format = self.calendar_fields.date_start.kind == 'time' ? 'HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss';
data[self.date_start] = event_obj.start_date.toString(date_format);
if (self.date_stop) {
data[self.date_stop] = event_obj.end_date.toString(date_format);
}
if (self.date_delay) {
var tds = (event_obj.start_date.getOrdinalNumber() / 1e3 >> 0) - (event_obj.start_date.getOrdinalNumber() < 0);
var tde = (event_obj.end_date.getOrdinalNumber() / 1e3 >> 0) - (event_obj.end_date.getOrdinalNumber() < 0);
var n = (tde - tds) / (60 * 60);
if (n > self.day_length) {
var d = Math.floor(n / 24),
h = n % 24;
n = d * self.day_length + h;
}
data[self.date_delay] = n;
}
self.dataset.write(event_obj.id, data, self.load_scheduler);
} else {
// new Event.
return true;
}
});
scheduler.renderCalendar({
container: this.sidebar.navigator.element_id,
navigation: true,
date: scheduler._date,
handler: function(date, calendar) {
scheduler.setCurrentView(date, 'day');
}
});
},
resize_scheduler: function() {
scheduler.setCurrentView(scheduler._date);
},
load_scheduler: function() {
var self = this;
this.dataset.read_slice([], 0, false, function(events) {
if (self.session.locale_code) {
// TODO: replace $LAB
$LAB.setOptions({AlwaysPreserveOrder: true}).script([
'/base_calendar/static/lib/dhtmlxScheduler/sources/locale_' + self.session.locale_code + '.js',
'/base_calendar/static/lib/dhtmlxScheduler/sources/locale_recurring_' + self.session.locale_code + '.js'
]).wait(function() {
self.schedule_events(events);
});
} else {
self.schedule_events(events);
}
});
},
schedule_events: function(events) {
var self = this;
scheduler.clearAll();
//To parse Events we have to convert date Format
var res_events = [],
sidebar_items = [],
sidebar_ids = [];
for (var e = 0; e < events.length; e++) {
var evt = events[e];
if (!evt[this.date_start]) {
this.notification.warn("Start date is not defined for event :", evt['id']);
break;
}
if (this.fields[this.date_start]['type'] == 'date') {
evt[this.date_start] = openerp.base.parse_date(evt[this.date_start]).set({hour: 9}).toString('yyyy-MM-dd HH:mm:ss');
}
if (this.date_stop && evt[this.date_stop] && this.fields[this.date_stop]['type'] == 'date') {
evt[this.date_stop] = openerp.base.parse_date(evt[this.date_stop]).set({hour: 17}).toString('yyyy-MM-dd HH:mm:ss');
}
if (this.color_field) {
var user = evt[this.color_field];
if (user) {
if (_.indexOf(sidebar_ids, user[0]) === -1) {
sidebar_items.push({
id: user[0],
name: user[1],
// TODO: use color table
color: '#dddddd'
});
sidebar_ids.push(user[0]);
}
}
}
res_events.push(this.convert_event(evt));
}
scheduler.parse(res_events, 'json');
this.resize_scheduler();
this.sidebar.responsible.on_events_loaded(sidebar_items);
},
convert_event: function(event) {
var starts = event[this.date_start],
ends = event[this.date_delay] || 1,
span = 0,
res_text = '',
res_description = [];
var parse_start_date = openerp.base.parse_datetime(starts);
if (event[this.date_stop]) {
var parse_end_date = openerp.base.parse_datetime(event[this.date_stop]);
}
if (this.info_fields) {
var fld = event[this.info_fields[0]];
if (typeof fld == 'object') {
res_text = fld[fld.length -1];
} else {
res_text = fld;
}
var sliced_info_fields = this.info_fields.slice(1);
for (sl_fld in sliced_info_fields) {
var slc_fld = event[sliced_info_fields[sl_fld]];
if (typeof slc_fld == 'object') {
res_description.push(slc_fld[slc_fld.length - 1]);
} else {
if(slc_fld) res_description.push(slc_fld);
}
}
}
if (starts && ends) {
var n = 0,
h = ends;
if (ends == this.day_length) {
span = 1;
} else if (ends > this.day_length) {
n = ends / this.day_length;
h = ends % this.day_length;
n = parseInt(Math.floor(n));
if (h > 0) {
span = n + 1;
} else {
span = n;
}
}
var start = parse_start_date.setTime((parse_start_date.getTime() + (h * 60 * 60) + (n * 24 * 60 * 60)));
ends = parse_start_date;
}
if (starts && this.date_stop) {
ends = parse_end_date;
if (event[this.date_stop] == undefined) {
var start = parse_start_date.setTime((parse_start_date.getTime() + (h * 60 * 60) + (n * 24 * 60 * 60)));
ends = parse_start_date;
}
var tds = parse_start_date.getTime(),
tde = parse_end_date.getTime();
if (tds >= tde) {
tde = tds + 60 * 60;
parse_end_date.setTime(tde);
ends = parse_end_date;
}
n = (tde - tds) / (60 * 60);
if (n >= this.day_length) {
span = Math.ceil(n / 24);
}
}
return {
'start_date': parse_start_date.toString('yyyy-MM-dd HH:mm:ss'),
'end_date': ends.toString('yyyy-MM-dd HH:mm:ss'),
'text': res_text,
'id': event['id'],
'title': res_description.join()
}
},
do_search: function(domains, contexts, groupbys) {
var self = this;
this.rpc('/base/session/eval_domain_and_context', {
domains: domains,
contexts: contexts,
group_by_seq: groupbys
}, function (results) {
// TODO: handle non-empty results.group_by with read_group
self.dataset.context = self.context = results.context;
self.dataset.domain = self.domain = results.domain;
self.dataset.read_slice(_.keys(self.fields), 0, self.limit, function(events) {
self.schedule_events(events);
});
});
},
do_show: function () {
var self = this;
$.when(this.has_been_loaded).then(function() {
self.$element.show();
if (self.sidebar) {
self.sidebar.$element.show();
}
});
},
do_hide: function () {
this.$element.hide();
if (this.sidebar) {
this.sidebar.$element.hide();
}
},
popup_event: function(event_id) {
var self = this;
if (event_id) event_id = parseInt(event_id, 10);
var action = {
res_model: this.dataset.model,
res_id: event_id,
views: [[false, 'form']],
type: 'ir.actions.act_window',
view_type: 'form',
view_mode: 'form',
flags : {
search_view: false,
sidebar : false,
views_switcher : false,
action_buttons : false,
pager: false
}
}
var element_id = _.uniqueId("act_window_dialog");
var dialog = $('<div>', {
'id': element_id
}).dialog({
modal: true,
width: 'auto',
height: 'auto',
buttons: {
Cancel: function() {
$(this).dialog("destroy");
},
Save: function() {
var view_manager = action_manager.viewmanager;
var _dialog = this;
view_manager.views[view_manager.active_view].controller.do_save(function(r) {
$(_dialog).dialog("destroy");
// self.start();
self.load_scheduler();
})
}
}
});
var action_manager = new openerp.base.ActionManager(this, element_id);
action_manager.start();
action_manager.do_action(action);
//Default_get
if (!event_id) {
this.dataset.index = null;
}
}
});
//openerp.base.Action = openerp.base.Action.extend({
// do_action_window: function(action) {
// this._super.apply(this,arguments);
// for(var i = 0; i < action.views.length; i++) {
// if(action.views[i][1] == "calendar") {
// this.calendar_id = action.views[i][0];
// break;
// }
// }
// // IF there is a view calender
// // if(this.calendar_id
// },
//});
openerp.base_calendar.SidebarResponsible = openerp.base.Widget.extend({
init: function(parent, element_id, view) {
this._super(parent, element_id);
this.view = view;
},
on_events_loaded: function(users) {
this.$element.html(QWeb.render('CalendarView.sidebar.responsible', { users : users }));
// TODO: bind checkboxes reload sheduler
}
});
openerp.base_calendar.SidebarNavigator = openerp.base.Widget.extend({
init: function(parent, element_id, view) {
this._super(parent, element_id);
this.view = view;
},
on_events_loaded: function(events) {
}
});
};

View File

@ -1,33 +1,26 @@
<template>
<t t-name="CalendarView">
<h3 class="title"><t t-esc="view.fields_view.arch.attrs.string"/></h3>
<table class="calendar-view" width="100%" height="100%" cellspacing="0" cellpadding="0">
<tr>
<td>
<div style="height: 1000px;width: 100%;">
<div id="calendar-sidebar">
</div>
</div>
</td>
<td style="width:85%;" align="left">
<div id="openerp_scheduler" class="dhx_cal_container" style="height: 1000px;width: 100%;">
<div class="dhx_cal_navline">
<div class="dhx_cal_prev_button"/>
<div class="dhx_cal_next_button"/>
<div class="dhx_cal_today_button"/>
<div class="dhx_cal_date"/>
<div class="dhx_minical_icon" id="dhx_minical_icon"/>
<div class="dhx_cal_tab" name="day_tab" style="right:204px;"/>
<div class="dhx_cal_tab" name="week_tab" style="right:140px;"/>
<div class="dhx_cal_tab" name="month_tab" style="right:76px;"/>
</div>
<div class="dhx_cal_header">
</div>
<div class="dhx_cal_data">
</div>
</div>
</td>
</tr>
</table>
</t>
<t t-name="CalendarView">
<h3 class="title"><t t-esc="fields_view.arch.attrs.string"/></h3>
<div id="openerp_scheduler" class="dhx_cal_container" style="height: 1000px;">
<div class="dhx_cal_navline">
<div class="dhx_cal_prev_button"/>
<div class="dhx_cal_next_button"/>
<div class="dhx_cal_today_button"/>
<div class="dhx_cal_date"/>
<div class="dhx_cal_tab" name="day_tab" style="right:204px;"/>
<div class="dhx_cal_tab" name="week_tab" style="right:140px;"/>
<div class="dhx_cal_tab" name="month_tab" style="right:76px;"/>
</div>
<div class="dhx_cal_header">
</div>
<div class="dhx_cal_data">
</div>
</div>
</t>
<t t-name="CalendarView.sidebar.responsible">
<div t-foreach="users" t-as="user" class="oe_calendar_responsible" t-attf-style="background: #{user.color}">
<input type="checkbox" name="selection" t-att-value="user.id"/>
<span><t t-esc="user.name"/></span>
</div>
</t>
</template>

View File

@ -5,7 +5,7 @@
openerp.base.diagram = function (openerp) {
openerp.base.views.add('diagram', 'openerp.base.DiagramView');
openerp.base.DiagramView = openerp.base.Controller.extend({
openerp.base.DiagramView = openerp.base.Widget.extend({
init: function(view_manager, session, element_id, dataset, view_id){
this._super(session, element_id);
this.view_manager = view_manager;

View File

@ -5,7 +5,7 @@
openerp.base_gantt = function (openerp) {
QWeb.add_template('/base_gantt/static/src/xml/base_gantt.xml');
openerp.base.views.add('gantt', 'openerp.base_gantt.GanttView');
openerp.base_gantt.GanttView = openerp.base.Controller.extend({
openerp.base_gantt.GanttView = openerp.base.Widget.extend({
init: function(view_manager, session, element_id, dataset, view_id) {

View File

@ -1,5 +1,5 @@
openerp.web_mobile.chrome_mobile = function(openerp) {
openerp.web_mobile.Shortcuts = openerp.base.Controller.extend({
openerp.web_mobile.Shortcuts = openerp.base.Widget.extend({
init: function(session, element_id) {
this._super(session, element_id);
},
@ -19,7 +19,7 @@ openerp.web_mobile.Shortcuts = openerp.base.Controller.extend({
this.listview.start();
}
});
openerp.web_mobile.Header = openerp.base.Controller.extend({
openerp.web_mobile.Header = openerp.base.Widget.extend({
init: function(session, element_id) {
this._super(session, element_id);
},
@ -36,7 +36,7 @@ openerp.web_mobile.Header = openerp.base.Controller.extend({
}
}
});
openerp.web_mobile.Secondary = openerp.base.Controller.extend({
openerp.web_mobile.Secondary = openerp.base.Widget.extend({
init: function(session, element_id, secondary_menu_id) {
this._super(session, element_id);
this.data = secondary_menu_id;
@ -74,7 +74,7 @@ openerp.web_mobile.Secondary = openerp.base.Controller.extend({
}
});
openerp.web_mobile.Menu = openerp.base.Controller.extend({
openerp.web_mobile.Menu = openerp.base.Widget.extend({
init: function(session, element_id, secondary_menu_id) {
this._super(session, element_id);
this.secondary_menu_id = secondary_menu_id;
@ -106,7 +106,7 @@ openerp.web_mobile.Menu = openerp.base.Controller.extend({
this.secondary.start();
}
});
openerp.web_mobile.Options = openerp.base.Controller.extend({
openerp.web_mobile.Options = openerp.base.Widget.extend({
init: function(session, element_id) {
this._super(session, element_id);
},
@ -121,7 +121,7 @@ openerp.web_mobile.Options = openerp.base.Controller.extend({
this.login.start();
}
});
openerp.web_mobile.Login = openerp.base.Controller.extend({
openerp.web_mobile.Login = openerp.base.Widget.extend({
init: function(session, element_id) {
this._super(session, element_id);
},
@ -129,7 +129,7 @@ openerp.web_mobile.Login = openerp.base.Controller.extend({
var self = this;
jQuery("#oe_header").children().remove();
this.rpc("/base/session/get_databases_list", {}, function(result) {
this.rpc("/base/database/get_list", {}, function(result) {
self.db_list = result.db_list;
self.$element.html(QWeb.render("Login", self));
self.$element.find('#database').click(self.on_db_select);
@ -190,7 +190,7 @@ openerp.web_mobile.Login = openerp.base.Controller.extend({
});
}
});
openerp.web_mobile.MobileWebClient = openerp.base.Controller.extend({
openerp.web_mobile.MobileWebClient = openerp.base.Widget.extend({
init: function(element_id) {
var self = this;
this._super(null, element_id);
@ -214,4 +214,4 @@ openerp.web_mobile.mobilewebclient = function(element_id) {
client.start();
return client;
};
}
}

View File

@ -1,5 +1,5 @@
openerp.web_mobile.form_mobile = function (openerp) {
openerp.web_mobile.FormView = openerp.base.Controller.extend({
openerp.web_mobile.FormView = openerp.base.Widget.extend({
init: function(session, element_id, list_id, action) {
this._super(session, element_id);
this.list_id = list_id;

View File

@ -1,5 +1,5 @@
openerp.web_mobile.list_mobile = function (openerp) {
openerp.web_mobile.ListView = openerp.base.Controller.extend({
openerp.web_mobile.ListView = openerp.base.Widget.extend({
init: function(session, element_id, list_id) {
this._super(session, element_id);
this.list_id = list_id;

View File

@ -374,7 +374,10 @@ class HttpRequest(object):
akw = dict([(key, kw[key] if isinstance(kw[key], basestring) else type(kw[key])) for key in kw.keys()])
print "POST --> %s.%s %s %r" % (controller.__class__.__name__, f.__name__, request, akw)
r = f(controller, self, **kw)
print "<--", r
if isinstance(r, str):
print "<--", len(r), 'bytes'
else:
print "<--", len(r), 'characters'
print
return r