diff --git a/addons/base/__openerp__.py b/addons/base/__openerp__.py index da2b345d217..b93e4036f8e 100644 --- a/addons/base/__openerp__.py +++ b/addons/base/__openerp__.py @@ -3,4 +3,38 @@ "version" : "2.0", "depends" : [], 'active': True, + 'js' : [ + "static/lib/datejs/date-en-US.js", + "static/lib/jquery/jquery-1.5.2.js", + "static/lib/jquery.ba-bbq/jquery.ba-bbq.js", + "static/lib/jquery.contextmenu/jquery.contextmenu.r2.packed.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", + "static/lib/jquery.ui/js/jquery-ui-timepicker-addon.js", + "static/lib/jquery.ui.notify/js/jquery.notify.js", + "static/lib/json/json2.js", + "static/lib/qweb/qweb2.js", + "static/lib/underscore/underscore.js", + "static/lib/underscore/underscore.string.js", + "static/src/js/boot.js", + "static/src/js/core.js", + "static/src/js/dates.js", + "static/src/js/chrome.js", + "static/src/js/views.js", + "static/src/js/data.js", + "static/src/js/data_export.js", + "static/src/js/form.js", + "static/src/js/list.js", + "static/src/js/list-editable.js", + "static/src/js/search.js", + "static/src/js/view_tree.js", + ], + 'css' : [ + "static/lib/jquery.superfish/css/superfish.css", + "static/lib/jquery.ui/css/smoothness/jquery-ui-1.8.9.custom.css", + "static/lib/jquery.ui.notify/css/ui.notify.css", + "static/src/css/base.css", + "static/src/css/data_export.css", + ], } diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 36dd21901a9..fcf673b50d7 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -10,6 +10,8 @@ import openerpweb.ast import openerpweb.nonliterals import cherrypy +import csv +import xml.dom.minidom # Should move to openerpweb.Xml2Json class Xml2Json: @@ -54,6 +56,99 @@ class Xml2Json: # OpenERP Web base Controllers #---------------------------------------------------------- +def manifest_glob(addons, key): + files = [] + for addon in addons: + globlist = openerpweb.addons_manifest.get(addon, {}).get(key, []) + print globlist + for pattern in globlist: + for path in glob.glob(os.path.join(openerpweb.path_addons, addon, pattern)): + files.append(path[len(openerpweb.path_addons):]) + return files + +def concat_files(file_list): + """ Concatenate file content + return (concat,timestamp) + concat: concatenation of file content + timestamp: max(os.path.getmtime of file_list) + """ + root = openerpweb.path_root + files_content = [] + files_timestamp = 0 + for i in file_list: + fname = os.path.join(root, i) + ftime = os.path.getmtime(fname) + if ftime > files_timestamp: + files_timestamp = ftime + files_content = open(fname).read() + files_concat = "".join(files_content) + return files_concat + +class WebClient(openerpweb.Controller): + _cp_path = "/base/webclient" + + @openerpweb.jsonrequest + def csslist(self, req, mods='base'): + return manifest_glob(mods.split(','), 'css') + + @openerpweb.jsonrequest + def jslist(self, req, mods='base'): + return manifest_glob(mods.split(','), 'js') + + @openerpweb.httprequest + def css(self, req, mods='base'): + cherrypy.response.headers['Content-Type'] = 'text/css' + files = manifest_glob(mods.split(','), 'css') + concat = concat_files(files)[0] + # TODO request set the Date of last modif and Etag + return concat + + @openerpweb.httprequest + def js(self, req, mods='base'): + cherrypy.response.headers['Content-Type'] = 'application/javascript' + files = manifest_glob(mods.split(','), 'js') + concat = concat_files(files)[0] + # TODO request set the Date of last modif and Etag + return concat + + @openerpweb.httprequest + def home(self, req): + template =""" + + + + OpenERP + %s + + + %s + + + + + """.replace('\n'+' '*8,'\n') + + # script tags + jslist = ['/base/webclient/js'] + if 1: # debug == 1 + jslist = manifest_glob(['base'], 'js') + js = "\n ".join([''%i for i in jslist]) + + # css tags + csslist = ['/base/webclient/css'] + if 1: # debug == 1 + csslist = manifest_glob(['base'], 'css') + css = "\n ".join([''%i for i in csslist]) + r = template % (js, css) + return r + class Database(openerpweb.Controller): _cp_path = "/base/database" @@ -71,37 +166,6 @@ class Database(openerpweb.Controller): class Session(openerpweb.Controller): _cp_path = "/base/session" - def manifest_glob(self, addons, key): - files = [] - for addon in addons: - globlist = openerpweb.addons_manifest.get(addon, {}).get(key, []) - - files.extend([ - resource_path[len(openerpweb.path_addons):] - for pattern in globlist - for resource_path in glob.glob(os.path.join( - openerpweb.path_addons, addon, pattern)) - ]) - return files - - def concat_files(self, file_list): - """ Concatenate file content - return (concat,timestamp) - concat: concatenation of file content - timestamp: max(os.path.getmtime of file_list) - """ - root = openerpweb.path_root - files_content = [] - files_timestamp = 0 - for i in file_list: - fname = os.path.join(root, i) - ftime = os.path.getmtime(fname) - if ftime > files_timestamp: - files_timestamp = ftime - files_content = open(fname).read() - files_concat = "".join(files_content) - return files_concat - @openerpweb.jsonrequest def login(self, req, db, login, password): req.session.login(db, login, password) @@ -118,31 +182,12 @@ class Session(openerpweb.Controller): @openerpweb.jsonrequest def modules(self, req): - return {"modules": [name - for name, manifest in openerpweb.addons_manifest.iteritems() - if manifest.get('active', True)]} - - @openerpweb.jsonrequest - def csslist(self, req, mods='base'): - return {'files': self.manifest_glob(mods.split(','), 'css')} - - @openerpweb.jsonrequest - def jslist(self, req, mods='base'): - return {'files': self.manifest_glob(mods.split(','), 'js')} - - def css(self, req, mods='base'): - files = self.manifest_glob(mods.split(','), 'css') - concat = self.concat_files(files)[0] - # TODO request set the Date of last modif and Etag - return concat - css.exposed = True - - def js(self, req, mods='base'): - files = self.manifest_glob(mods.split(','), 'js') - concat = self.concat_files(files)[0] - # TODO request set the Date of last modif and Etag - return concat - js.exposed = True + # TODO query server for installed web modules + mods = [] + for name, manifest in openerpweb.addons_manifest.items(): + if name != 'base' and manifest.get('active', True): + mods.append(name) + return mods @openerpweb.jsonrequest def eval_domain_and_context(self, req, contexts, domains, @@ -179,7 +224,7 @@ class Session(openerpweb.Controller): context, domain = eval_context_and_domain(req.session, openerpweb.nonliterals.CompoundContext(*(contexts or [])), openerpweb.nonliterals.CompoundDomain(*(domains or []))) - + group_by_sequence = [] for candidate in (group_by_seq or []): ctx = req.session.eval_context(candidate, context) @@ -190,7 +235,7 @@ class Session(openerpweb.Controller): group_by_sequence.append(group_by) else: group_by_sequence.extend(group_by) - + return { 'context': context, 'domain': domain, @@ -203,7 +248,7 @@ class Session(openerpweb.Controller): This method store an action object in the session object and returns an integer identifying that action. The method get_session_action() can be used to get back the action. - + :param the_action: The action to save in the session. :type the_action: anything :return: A key identifying the saved action. @@ -226,7 +271,7 @@ class Session(openerpweb.Controller): """ Gets back a previously saved action. This method can return None if the action was saved since too much time (this case should be handled in a smart way). - + :param key: The key given by save_session_action() :type key: integer :return: The saved action or None. @@ -264,7 +309,7 @@ def clean_action(action, session): action['domain'] = eval( action['domain'], session.evaluation_context( - action['context'])) or [] + action.get('context', {}))) or [] if 'flags' not in action: # Set empty flags dictionary for web client. action['flags'] = dict() @@ -361,7 +406,7 @@ class Menu(openerpweb.Controller): menu_items = Menus.read(menu_ids, ['name', 'sequence', 'parent_id'], context) menu_root = {'id': False, 'name': 'root', 'parent_id': [-1, '']} menu_items.append(menu_root) - + # make a tree using parent_id menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items) for menu_item in menu_items: @@ -468,7 +513,7 @@ class DataSet(openerpweb.Controller): record_map = dict((record['id'], record) for record in records) return [record_map[id] for id in ids if record_map.get(id)] - + @openerpweb.jsonrequest def load(self, req, model, id, fields): m = req.session.model(model) @@ -631,7 +676,7 @@ class View(openerpweb.Controller): except ValueError: # not a literal return openerpweb.nonliterals.Domain(session, domain) - + def parse_context(self, context, session): """ Parses an arbitrary string containing a context, transforms it to either a literal context or a :class:`openerpweb.nonliterals.Context` @@ -721,6 +766,34 @@ class SearchView(View): if field.get('context'): field["context"] = self.parse_domain(field["context"], req.session) return {'fields': fields} + + @openerpweb.jsonrequest + def get_filters(self, req, model): + Model = req.session.model("ir.filters") + filters = Model.get_filters(model) + for filter in filters: + filter["context"] = req.session.eval_context(self.parse_context(filter["context"], req.session)) + filter["domain"] = req.session.eval_domain(self.parse_domain(filter["domain"], req.session)) + return filters + + @openerpweb.jsonrequest + def save_filter(self, req, model, name, context_to_save, domain): + Model = req.session.model("ir.filters") + ctx = openerpweb.nonliterals.CompoundContext(context_to_save) + ctx.session = req.session + ctx = ctx.evaluate() + domain = openerpweb.nonliterals.CompoundDomain(domain) + domain.session = req.session + domain = domain.evaluate() + uid = req.session._uid + context = req.session.eval_context(req.context) + to_return = Model.create_or_replace({"context": ctx, + "domain": domain, + "model_id": model, + "name": name, + "user_id": uid + }, context) + return to_return class Binary(openerpweb.Controller): _cp_path = "/base/binary" @@ -832,4 +905,229 @@ 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): + import StringIO + fp = StringIO.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): + import StringIO + 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.StringIO() + workbook.save(fp) + fp.seek(0) + data = fp.read() + fp.close() + #return data.decode('ISO-8859-1') + return unicode(data, 'utf-8', 'replace') + +def node_attributes(node): + attrs = node.attributes + + if not attrs: + return {} + # localName can be a unicode string, we're using attribute names as + # **kwargs keys and python-level kwargs don't take unicode keys kindly + # (they blow up) so we need to ensure all keys are ``str`` + return dict([(str(attrs.item(i).localName), attrs.item(i).nodeValue) + for i in range(attrs.length)]) + +class Export(View): + _cp_path = "/base/export" + + def fields_get(self, req, model): + Model = req.session.model(model) + fields = Model.fields_get(False, req.session.eval_context(req.context)) + return fields + + @openerpweb.jsonrequest + def get_fields(self, req, model, prefix='', name= '', field_parent=None, params={}): + import_compat = params.get("import_compat", False) + + fields = self.fields_get(req, model) + field_parent_type = params.get("parent_field_type",False) + + if import_compat and field_parent_type and field_parent_type == "many2one": + fields = {} + + fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}}) + records = [] + fields_order = fields.keys() + fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', ''))) + + for index, field in enumerate(fields_order): + value = fields[field] + record = {} + if import_compat and value.get('readonly', False): + ok = False + for sl in value.get('states', {}).values(): + for s in sl: + ok = ok or (s==['readonly',False]) + if not ok: continue + + id = prefix + (prefix and '/'or '') + field + nm = name + (name and '/' or '') + value['string'] + record.update(id=id, string= nm, action='javascript: void(0)', + target=None, icon=None, children=[], field_type=value.get('type',False), required=value.get('required', False)) + records.append(record) + + if len(nm.split('/')) < 3 and value.get('relation', False): + if import_compat: + ref = value.pop('relation') + cfields = self.fields_get(req, ref) + if (value['type'] == 'many2many'): + record['children'] = [] + record['params'] = {'model': ref, 'prefix': id, 'name': nm} + + elif value['type'] == 'many2one': + record['children'] = [id + '/id', id + '/.id'] + record['params'] = {'model': ref, 'prefix': id, 'name': nm} + + else: + cfields_order = cfields.keys() + cfields_order.sort(lambda x,y: -cmp(cfields[x].get('string', ''), cfields[y].get('string', ''))) + children = [] + for j, fld in enumerate(cfields_order): + cid = id + '/' + fld + cid = cid.replace(' ', '_') + children.append(cid) + record['children'] = children or [] + record['params'] = {'model': ref, 'prefix': id, 'name': nm} + else: + ref = value.pop('relation') + cfields = self.fields_get(req, ref) + cfields_order = cfields.keys() + cfields_order.sort(lambda x,y: -cmp(cfields[x].get('string', ''), cfields[y].get('string', ''))) + children = [] + for j, fld in enumerate(cfields_order): + cid = id + '/' + fld + cid = cid.replace(' ', '_') + children.append(cid) + record['children'] = children or [] + record['params'] = {'model': ref, 'prefix': id, 'name': nm} + + records.reverse() + return records + + @openerpweb.jsonrequest + def save_export_lists(self, req, name, model, field_list): + result = {'resource':model, 'name':name, 'export_fields': []} + for field in field_list: + result['export_fields'].append((0, 0, {'name': field})) + return req.session.model("ir.exports").create(result, req.session.eval_context(req.context)) + + @openerpweb.jsonrequest + def exist_export_lists(self, req, model): + export_model = req.session.model("ir.exports") + return export_model.read(export_model.search([('resource', '=', model)]), ['name']) + + @openerpweb.jsonrequest + def delete_export(self, req, export_id): + req.session.model("ir.exports").unlink(export_id, req.session.eval_context(req.context)) + return True + + @openerpweb.jsonrequest + def namelist(self,req, model, export_id): + + result = self.get_data(req, model, req.session.eval_context(req.context)) + ir_export_obj = req.session.model("ir.exports") + ir_export_line_obj = req.session.model("ir.exports.line") + + field = ir_export_obj.read(export_id) + fields = ir_export_line_obj.read(field['export_fields']) + + name_list = {} + [name_list.update({field['name']: result.get(field['name'])}) for field in fields] + return name_list + + def get_data(self, req, model, context=None): + ids = [] + context = context or {} + fields_data = {} + proxy = req.session.model(model) + fields = self.fields_get(req, model) + if not ids: + f1 = proxy.fields_view_get(False, 'tree', context)['fields'] + f2 = proxy.fields_view_get(False, 'form', context)['fields'] + + fields = dict(f1) + fields.update(f2) + fields.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}}) + + def rec(fields): + _fields = {'id': 'ID' , '.id': 'Database ID' } + def model_populate(fields, prefix_node='', prefix=None, prefix_value='', level=2): + fields_order = fields.keys() + fields_order.sort(lambda x,y: -cmp(fields[x].get('string', ''), fields[y].get('string', ''))) + + for field in fields_order: + fields_data[prefix_node+field] = fields[field] + if prefix_node: + fields_data[prefix_node + field]['string'] = '%s%s' % (prefix_value, fields_data[prefix_node + field]['string']) + st_name = fields[field]['string'] or field + _fields[prefix_node+field] = st_name + if fields[field].get('relation', False) and level>0: + fields2 = self.fields_get(req, fields[field]['relation']) + fields2.update({'id': {'string': 'ID'}, '.id': {'string': 'Database ID'}}) + model_populate(fields2, prefix_node+field+'/', None, st_name+'/', level-1) + model_populate(fields) + return _fields + return rec(fields) + + @openerpweb.jsonrequest + def export_data(self, req, model, fields, ids, domain, import_compat=False, export_format="csv", context=None): + context = req.session.eval_context(req.context) + modle_obj = req.session.model(model) + ids = ids or modle_obj.search(domain, context=context) + + field = fields.keys() + result = modle_obj.export_data(ids, field , context).get('datas',[]) + + if not import_compat: + field = [val.strip() for val in fields.values()] + + if export_format == 'xls': + return export_xls(field, result) + else: + return export_csv(field, result) + diff --git a/addons/base/static/lib/LABjs/LAB.js b/addons/base/static/lib/LABjs/LAB.js deleted file mode 100644 index 1c3c85bd7a3..00000000000 --- a/addons/base/static/lib/LABjs/LAB.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! LAB.js (LABjs :: Loading And Blocking JavaScript) - v1.2.0 (c) Kyle Simpson - MIT License -*/ -(function(p){var q="string",w="head",L="body",M="script",u="readyState",j="preloaddone",x="loadtrigger",N="srcuri",E="preload",Z="complete",y="done",z="which",O="preserve",F="onreadystatechange",ba="onload",P="hasOwnProperty",bb="script/cache",Q="[object ",bw=Q+"Function]",bx=Q+"Array]",e=null,h=true,i=false,k=p.document,bc=p.location,bd=p.ActiveXObject,A=p.setTimeout,be=p.clearTimeout,R=function(a){return k.getElementsByTagName(a)},S=Object.prototype.toString,G=function(){},r={},T={},bf=/^[^?#]*\//.exec(bc.href)[0],bg=/^\w+\:\/\/\/?[^\/]+/.exec(bf)[0],by=R(M),bh=p.opera&&S.call(p.opera)==Q+"Opera]",bi=("MozAppearance"in k.documentElement.style),bj=(k.createElement(M).async===true),v={cache:!(bi||bh),order:bi||bh||bj,xhr:h,dupe:h,base:"",which:w};v[O]=i;v[E]=h;r[w]=k.head||R(w);r[L]=R(L);function B(a){return S.call(a)===bw}function U(a,b){var c=/^\w+\:\/\//,d;if(typeof a!=q)a="";if(typeof b!=q)b="";d=((/^\/\//.test(a))?bc.protocol:"")+a;d=(c.test(d)?"":b)+d;return((c.test(d)?"":(d.charAt(0)==="/"?bg:bf))+d)}function bz(a){return(U(a).indexOf(bg)===0)}function bA(a){var b,c=-1;while(b=by[++c]){if(typeof b.src==q&&a===U(b.src)&&b.type!==bb)return h}return i}function H(t,l){t=!(!t);if(l==e)l=v;var bk=i,C=t&&l[E],bl=C&&l.cache,I=C&&l.order,bm=C&&l.xhr,bB=l[O],bC=l.which,bD=l.base,bn=G,J=i,D,s=h,m={},K=[],V=e;C=bl||bm||I;function bo(a,b){if((a[u]&&a[u]!==Z&&a[u]!=="loaded")||b[y]){return i}a[ba]=a[F]=e;return h}function W(a,b,c){c=!(!c);if(!c&&!(bo(a,b)))return;b[y]=h;for(var d in m){if(m[P](d)&&!(m[d][y]))return}bk=h;bn()}function bp(a){if(B(a[x])){a[x]();a[x]=e}}function bE(a,b){if(!bo(a,b))return;b[j]=h;A(function(){r[b[z]].removeChild(a);bp(b)},0)}function bF(a,b){if(a[u]===4){a[F]=G;b[j]=h;A(function(){bp(b)},0)}}function X(b,c,d,g,f,n){var o=b[z];A(function(){if("item"in r[o]){if(!r[o][0]){A(arguments.callee,25);return}r[o]=r[o][0]}var a=k.createElement(M);if(typeof d==q)a.type=d;if(typeof g==q)a.charset=g;if(B(f)){a[ba]=a[F]=function(){f(a,b)};a.src=c;if(bj){a.async=i}}r[o].insertBefore(a,(o===w?r[o].firstChild:e));if(typeof n==q){a.text=n;W(a,b,h)}},0)}function bq(a,b,c,d){T[a[N]]=h;X(a,b,c,d,W)}function br(a,b,c,d){var g=arguments;if(s&&a[j]==e){a[j]=i;X(a,b,bb,d,bE)}else if(!s&&a[j]!=e&&!a[j]){a[x]=function(){br.apply(e,g)}}else if(!s){bq.apply(e,g)}}function bs(a,b,c,d){var g=arguments,f;if(s&&a[j]==e){a[j]=i;f=a.xhr=(bd?new bd("Microsoft.XMLHTTP"):new p.XMLHttpRequest());f[F]=function(){bF(f,a)};f.open("GET",b);f.send("")}else if(!s&&a[j]!=e&&!a[j]){a[x]=function(){bs.apply(e,g)}}else if(!s){T[a[N]]=h;X(a,b,c,d,e,a.xhr.responseText);a.xhr=e}}function bt(a){if(typeof a=="undefined"||!a)return;if(a.allowDup==e)a.allowDup=l.dupe;var b=a.src,c=a.type,d=a.charset,g=a.allowDup,f=U(b,bD),n,o=bz(f);if(typeof d!=q)d=e;g=!(!g);if(!g&&((T[f]!=e)||(s&&m[f])||bA(f))){if(m[f]!=e&&m[f][j]&&!m[f][y]&&o){W(e,m[f],h)}return}if(m[f]==e)m[f]={};n=m[f];if(n[z]==e)n[z]=bC;n[y]=i;n[N]=f;J=h;if(!I&&bm&&o)bs(n,f,c,d);else if(!I&&bl)br(n,f,c,d);else bq(n,f,c,d)}function Y(a){if(t&&!I)K.push(a);if(!t||C)a()}function bu(a){var b=[],c;for(c=-1;++c - - - - OpenERP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index d220c8b406b..b173386e234 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -30,6 +30,9 @@ body.openerp { .openerp .oe-number { text-align: right !important; } +.openerp .oe_hide { + display: none !important; +} /* STATES */ .openerp .on_logged { @@ -595,6 +598,10 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%, border: 1px solid silver; } +.openerp .oe-listview thead table { + width: 100%; + border: none; +} .openerp .oe-listview tr.odd { background-color: #f3f3f3; } @@ -604,6 +611,10 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%, .openerp .oe-listview tbody tr:hover { background-color: #eae9f0; } +.openerp .oe-listview thead table tr, +.openerp .oe-listview thead table tr:hover { + background: none; +} .openerp .oe-listview td, .openerp .oe-listview th { @@ -643,9 +654,11 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%, .openerp .oe-listview th.oe-actions { text-align: left; + white-space: nowrap; } .openerp .oe-listview th.oe-list-pager { text-align: right; + white-space: nowrap; } .openerp .oe-list-pager .oe-pager-state { cursor: pointer; @@ -675,29 +688,29 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%, rounded corners are a pain on tables: need to round not only table, but also on the first and last children of the first and last row */ -.openerp .oe-listview table { +.openerp .oe-listview table.oe-listview-content { -webkit-border-radius: 7px; -moz-border-radius: 7px; border-radius: 7px; } -.openerp .oe-listview table thead tr:first-child th:first-child { +.openerp .oe-listview table.oe-listview-content thead tr:first-child th:first-child { -webkit-border-top-left-radius: 7px; -moz-border-radius-topleft: 7px; border-top-left-radius: 7px; } -.openerp .oe-listview table thead tr:first-child th:last-child { +.openerp .oe-listview table.oe-listview-content thead tr:first-child th:last-child { -webkit-border-top-right-radius: 7px; -moz-border-radius-topright: 7px; border-top-right-radius: 7px; } -.openerp .oe-listview table tfoot td:first-child, -.openerp .oe-listview table tbody:last-child tr:last-child th:first-child { +.openerp .oe-listview table.oe-listview-content tfoot td:first-child, +.openerp .oe-listview table.oe-listview-content tbody:last-child tr:last-child th:first-child { -webkit-border-bottom-left-radius: 7px; -moz-border-radius-bottomleft: 7px; border-bottom-left-radius: 7px; } -.openerp .oe-listview table tfoot td:last-child, -.openerp .oe-listview table tbody:last-child tr:last-child td:last-child { +.openerp .oe-listview table.oe-listview-content tfoot td:last-child, +.openerp .oe-listview table.oe-listview-content tbody:last-child tr:last-child td:last-child { -webkit-border-bottom-right-radius: 7px; -moz-border-radius-bottomright: 7px; border-bottom-right-radius: 7px; @@ -1010,6 +1023,10 @@ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */ .openerp .closed-sidebar .toggle-sidebar { border-left: none; } +.openerp li.oe_sidebar_print { + padding-left: 20px; + background: 1px 3px url(../img/icons/gtk-print.png) no-repeat; +} .openerp.kitten-mode-activated .main_table { background: url(http://placekitten.com/g/1500/800) repeat; diff --git a/addons/base/static/src/css/data_export.css b/addons/base/static/src/css/data_export.css new file mode 100644 index 00000000000..7b4b9707b39 --- /dev/null +++ b/addons/base/static/src/css/data_export.css @@ -0,0 +1,85 @@ +.openerp .oe_export_row tr{ + background-color: #FFFFFF; + font-size: 0.8em; + height: 22px; +} + +.openerp tr.ui-selected td { + background-color: #CCCCCC; +} + +.openerp .oe_export_requiredfield { + background-color: #D2D2FF; +} + +.openerp .oe_export_readonlyfield{ + background-color: #999999; +} + +.openerp .oe_export_row:hover{ + background-color: #F3F3F3; +} + +.openerp .oe_export_fields_selector_export { + width: 100%; + height: 400px; +} + +.openerp .oe_export_fields_selector_left { + width: 50%; +} + +.openerp div#left_field_panel { + overflow: scroll; + width: 100%; + height: 400px; + border: solid #999999 1px; +} + +.openerp .oe_export_fields_selector_center { + width: 102px; +} + +.openerp .oe_export_fields_selector_right { + width: 45%; + height: 400px; +} + +.openerp .oe_export_fields_selector_export select{ + width: 100%; + height: 100%; +} + +.openerp .oe_export_tree_header{ + border: 0.5px solid #E3E3E3; + text-align: left; + white-space: nowrap; + padding: 4px 5px; + background: url(/base/static/src/img/header.gif); +} + + +.openerp table.tree_grid{ + border: 1px solid #E3E3E3; + text-align: left; + white-space: nowrap; + background-color:#E3E3E3; + border-collapse: collapse; + width: 100%; +} + +.openerp table.tree_grid a:hover { + color: blue; + border: none; +} + +.openerp table.tree_grid a { + color: #5F5C5C; + border: none; + display: block; +} + +.openerp .oe_export_button_export { + border: 1px solid #006; + background-color: #F3F3F3; +} diff --git a/addons/base/static/src/img/collapse.gif b/addons/base/static/src/img/collapse.gif new file mode 100644 index 00000000000..759e72ebcfe Binary files /dev/null and b/addons/base/static/src/img/collapse.gif differ diff --git a/addons/base/static/src/img/expand.gif b/addons/base/static/src/img/expand.gif new file mode 100644 index 00000000000..ca98d0b5f2b Binary files /dev/null and b/addons/base/static/src/img/expand.gif differ diff --git a/addons/base/static/src/img/header.gif b/addons/base/static/src/img/header.gif new file mode 100644 index 00000000000..f40bd9c0f6a Binary files /dev/null and b/addons/base/static/src/img/header.gif differ diff --git a/addons/base/static/src/js/base.js b/addons/base/static/src/js/boot.js similarity index 53% rename from addons/base/static/src/js/base.js rename to addons/base/static/src/js/boot.js index 55e4929e4c0..c16f2d261f5 100644 --- a/addons/base/static/src/js/base.js +++ b/addons/base/static/src/js/boot.js @@ -1,71 +1,5 @@ -/*--------------------------------------------------------- - * John Resig Class, to be moved to openerp.base.Class - *---------------------------------------------------------*/ - -(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; - }; -})(); - //--------------------------------------------------------- -// OpenERP initialisation and black magic about the pool +// OpenERP Web Boostrap //--------------------------------------------------------- /** @@ -107,11 +41,13 @@ _session_id: "session" + session_counter++, screen: openerp.screen, sessions: openerp.sessions, - base: {} + base: {}, + web_mobile: {} }; openerp.sessions[new_instance._session_id] = new_instance; - if (!skip_init) + if (!skip_init){ openerp.base(new_instance); + } return new_instance; } }; @@ -121,8 +57,8 @@ // OpenERP base module split //--------------------------------------------------------- -/** @namespace */ openerp.base = function(instance) { + openerp.base.core(instance); openerp.base.dates(instance); openerp.base.chrome(instance); openerp.base.data(instance); @@ -135,9 +71,6 @@ openerp.base = function(instance) { if (openerp.base.list) { openerp.base.list(instance); } - if (openerp.base.tree) { - openerp.base.tree(instance); - } if (openerp.base. m2o) { openerp.base.m2o(instance); } @@ -147,6 +80,15 @@ openerp.base = function(instance) { if (openerp.base.list && openerp.base.list.editable) { openerp.base.list.editable(instance); } + if (openerp.web_mobile) { + openerp.web_mobile(instance); + } + if (openerp.base.view_tree) { + openerp.base.view_tree(instance); + } + if (openerp.base.data_export) { + openerp.base.data_export(instance); + } }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index 5eb0261100e..ed84c32071b 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -4,325 +4,15 @@ openerp.base.chrome = function(openerp) { -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) - }); -}; - -/** - * Base error for lookup failure - * - * @class - */ -openerp.base.NotFound = 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 = Class.extend( /** @lends openerp.base.Registry# */ { - /** - * Stores a mapping of arbitrary key (strings) to object paths (as strings - * as well). - * - * Resolves those paths at query time in order to always fetch the correct - * object, even if those objects have been overloaded/replaced after the - * registry was created. - * - * An object path is simply a dotted name from the openerp root to the - * object pointed to (e.g. ``"openerp.base.Session"`` for an OpenERP - * session object). - * - * @constructs - * @param {Object} mapping a mapping of keys to object-paths - */ - init: function (mapping) { - this.map = mapping || {}; - }, - /** - * Retrieves the object matching the provided key string. - * - * @param {String} key the key to fetch the object for - * @returns {Class} the stored class, to initialize - * - * @throws {openerp.base.KeyNotFound} if the object was not in the mapping - * @throws {openerp.base.ObjectNotFound} if the object path was invalid - */ - get_object: function (key) { - var path_string = this.map[key]; - if (path_string === undefined) { - throw new openerp.base.KeyNotFound(key); - } - - var object_match = openerp; - var path = path_string.split('.'); - // ignore first section - for(var i=1; i').text(v).appendTo($('body')); - } - if(notify && this.notification) { - this.notification.notify("Logging:",v); - } - } - } - - } -}); - -/** - * 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.BasicController.prototype.init; - var tmpclass = claz.extend(newer); - return tmpclass.extend(add || {}); -}; - -openerp.base.Notification = openerp.base.BasicController.extend({ - init: function(element_id) { - this._super(element_id); - this.$element.notify({ - speed: 500, - expires: 1500 - }); - }, - notify: function(title, text) { - this.$element.notify('create', { - title: title, - text: text - }); - }, - warn: function(title, text) { - this.$element.notify('create', 'oe_notification_alert', { - title: title, - text: text - }); - } -}); - -openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.base.Session# */{ +openerp.base.Session = openerp.base.Widget.extend( /** @lends openerp.base.Session# */{ /** * @constructs - * @extends openerp.base.BasicController * @param element_id to use for exception reporting * @param server * @param port */ - init: function(element_id, server, port) { - this._super(element_id); + init: function(parent, element_id, server, port) { + this._super(parent, element_id); this.server = (server == undefined) ? location.hostname : server; this.port = (port == undefined) ? location.port : port; this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp"; @@ -501,7 +191,7 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b for(var i=0; i').text(v).appendTo($('body')); + } + if(notify && this.notification) { + this.notification.notify("Logging:",v); + } + } + } + } +}); + +/** + * Base class for all visual components. Provides a lot of functionalities helpful + * for the management of a part of the DOM. + * + * Widget handles: + * - Rendering with QWeb. + * - Life-cycle management and parenting (when a parent is destroyed, all its children are + * destroyed too). + * - Insertion in DOM. + * + * Widget also extends SessionAware for ease of use. + * + * Guide to create implementations of the Widget class: + * ============================================== + * + * Here is a sample child class: + * + * MyWidget = openerp.base.Widget.extend({ + * // the name of the QWeb template to use for rendering + * template: "MyQWebTemplate", + * // identifier prefix, it is useful to put an obvious one for debugging + * identifier_prefix: 'my-id-prefix-', + * + * init: function(parent) { + * this._super(parent); + * // stuff that you want to init before the rendering + * }, + * start: function() { + * this._super(); + * // stuff you want to make after the rendering, `this.$element` holds a correct value + * this.$element.find(".my_button").click(/* an example of event binding * /); + * + * // if you have some asynchronous operations, it's a good idea to return + * // a promise in start() + * var promise = this.rpc(...); + * return promise; + * } + * }); + * + * Now this class can simply be used with the following syntax: + * + * var my_widget = new MyWidget(this); + * my_widget.appendTo($(".some-div")); + * + * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the + * DOM inside the ".some-div" div and its events were binded. + * + * And of course, when you don't need that widget anymore, just do: + * + * my_widget.stop(); + * + * That will kill the widget in a clean way and erase its content from the dom. + */ +openerp.base.Widget = openerp.base.SessionAware.extend({ + /** + * The name of the QWeb template that will be used for rendering. Must be + * redefined in subclasses or the default render() method can not be used. + * + * @type string + */ + template: null, + /** + * The prefix used to generate an id automatically. Should be redefined in + * subclasses. If it is not defined, a generic identifier will be used. + * + * @type string + */ + identifier_prefix: 'generic-identifier-', + /** + * @constructs + * Construct the widget and set its parent if a parent is given. + * + * @param {Widget} parent Binds the current instance to the given Widget instance. + * When that widget is destroyed by calling stop(), the current instance will be + * destroyed too. Can be null. + * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want + * to bind the current Widget to an already existing part of the DOM, which is not compatible + * with the DOM insertion methods provided by the current implementation of Widget. So + * for new components this argument should not be provided any more. + */ + init: function(parent, /** @deprecated */ element_id) { + this._super((parent || {}).session); + // if given an element_id, try to get the associated DOM element and save + // a reference in this.$element. Else just generate a unique identifier. + this.element_id = element_id; + this.element_id = this.element_id || _.uniqueId(this.identifier_prefix); + var tmp = document.getElementById(this.element_id); + this.$element = tmp ? $(tmp) : undefined; + + this.widget_parent = parent; + this.widget_children = []; + if(parent && parent.widget_children) { + parent.widget_children.push(this); + } + // useful to know if the widget was destroyed and should not be used anymore + this.widget_is_stopped = false; + }, + /** + * Render the current widget and appends it to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + appendTo: function(target) { + var self = this; + return this._render_and_insert(function(t) { + self.$element.appendTo(t); + }, target); + }, + /** + * Render the current widget and prepends it to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + prependTo: function(target) { + var self = this; + return this._render_and_insert(function(t) { + self.$element.prependTo(t); + }, target); + }, + /** + * Render the current widget and inserts it after to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + insertAfter: function(target) { + var self = this; + return this._render_and_insert(function(t) { + self.$element.insertAfter(t); + }, target); + }, + /** + * Render the current widget and inserts it before to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + insertBefore: function(target) { + var self = this; + return this._render_and_insert(function(t) { + self.$element.insertBefore(t); + }, target); + }, + _render_and_insert: function(insertion, target) { + var rendered = this.render(); + this.$element = $(rendered); + if (target instanceof openerp.base.Widget) + target = target.$element; + insertion(target); + return this.start(); + }, + /** + * Renders the widget using QWeb, `this.template` must be defined. + * The context given to QWeb contains the "widget" key that references `this`. + * + * @param {object} additional Additional context arguments to pass to the template. + */ + render: function (additional) { + return QWeb.render(this.template, _.extend({widget: this}, additional || {})); + }, + /** + * Method called after rendering. Mostly used to bind actions, perform asynchronous + * calls, etc... + * + * By convention, the method should return a promise to inform the caller when + * this widget has been initialized. + * + * @returns {jQuery.Deferred} + */ + start: function() { + if (!this.$element) { + var tmp = document.getElementById(this.element_id); + this.$element = tmp ? $(tmp) : undefined; + } + return $.Deferred().done().promise(); + }, + /** + * Destroys the current widget, also destory all its children before destroying itself. + */ + stop: function() { + _.each(_.clone(this.widget_children), function(el) { + el.stop(); + }); + if(this.$element != null) { + this.$element.remove(); + } + if (this.widget_parent && this.widget_parent.widget_children) { + this.widget_parent.widget_children = _.without(this.widget_parent.widget_children, this); + } + this.widget_parent = null; + this.widget_is_stopped = true; + }, + /** + * Inform the action manager to do an action. Of course, this suppose that + * the action manager can be found amongst the ancestors of the current widget. + * If that's not the case this method will simply return `false`. + */ + do_action: function(action, on_finished) { + if (this.widget_parent) { + return this.widget_parent.do_action(action, on_finished); + } + return false; + }, + rpc: function(url, data, success, error) { + var def = $.Deferred().then(success, error); + var self = this; + this._super(url, data). then(function() { + if (!self.widget_is_stopped) + def.resolve.apply(def, arguments); + }, function() { + if (!self.widget_is_stopped) + def.reject.apply(def, arguments); + }); + return def.promise(); + } +}); + +/** + * @deprecated + * For retro compatibility only, the only difference with is that render() uses + * directly `this` instead of context with a "widget" key. + */ +openerp.base.OldWidget = openerp.base.Widget.extend({ + render: function (additional) { + return QWeb.render(this.template, _.extend(_.extend({}, this), additional || {})); + } +}); + +} +// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index c1b64b93307..5545e32df17 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -18,7 +18,7 @@ openerp.base.serialize_sort = function (criterion) { }).join(', '); }; -openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.base.DataGroup# */{ +openerp.base.DataGroup = openerp.base.Widget.extend( /** @lends openerp.base.DataGroup# */{ /** * Management interface between views and grouped collections of OpenERP * records. @@ -30,7 +30,7 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas * content of the current grouping level. * * @constructs - * @extends openerp.base.Controller + * @extends openerp.base.Widget * * @param {openerp.base.Session} session Current OpenERP session * @param {String} model name of the model managed by this DataGroup @@ -39,18 +39,16 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas * @param {Array} group_by sequence of fields by which to group * @param {Number} [level=0] nesting level of the group */ - init: function(session, model, domain, context, group_by, level) { + init: function(parent, model, domain, context, group_by, level) { + this._super(parent, null); if (group_by) { if (group_by.length || context['group_by_no_leaf']) { - return new openerp.base.ContainerDataGroup( - session, model, domain, context, group_by, level); + return new openerp.base.ContainerDataGroup( this, model, domain, context, group_by, level); } else { - return new openerp.base.GrouplessDataGroup( - session, model, domain, context, level); + return new openerp.base.GrouplessDataGroup( this, model, domain, context, level); } } - this._super(session, null); this.model = model; this.context = context; this.domain = domain; @@ -59,8 +57,7 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas }, cls: 'DataGroup' }); -openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend( - /** @lends openerp.base.ContainerDataGroup# */ { +openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend( /** @lends openerp.base.ContainerDataGroup# */ { /** * * @constructs @@ -73,8 +70,8 @@ openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend( * @param group_by * @param level */ - init: function (session, model, domain, context, group_by, level) { - this._super(session, model, domain, context, null, level); + init: function (parent, model, domain, context, group_by, level) { + this._super(parent, model, domain, context, null, level); this.group_by = group_by; }, @@ -189,7 +186,7 @@ openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend( var child_context = _.extend({}, self.context, group.__context); return _.extend( new openerp.base.DataGroup( - self.session, self.model, group.__domain, + self, self.model, group.__domain, child_context, child_context.group_by, self.level + 1), group, {sort: self.sort}); @@ -197,8 +194,7 @@ openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend( }); } }); -openerp.base.GrouplessDataGroup = openerp.base.DataGroup.extend( - /** @lends openerp.base.GrouplessDataGroup# */ { +openerp.base.GrouplessDataGroup = openerp.base.DataGroup.extend( /** @lends openerp.base.GrouplessDataGroup# */ { /** * * @constructs @@ -210,18 +206,16 @@ openerp.base.GrouplessDataGroup = openerp.base.DataGroup.extend( * @param context * @param level */ - init: function (session, model, domain, context, level) { - this._super(session, model, domain, context, null, level); + init: function (parent, model, domain, context, level) { + this._super(parent, model, domain, context, null, level); }, list: function (fields, ifGroups, ifRecords) { ifRecords(_.extend( - new openerp.base.DataSetSearch(this.session, this.model), + new openerp.base.DataSetSearch(this, this.model), {domain: this.domain, context: this.context, _sort: this.sort})); } }); - -openerp.base.StaticDataGroup = openerp.base.GrouplessDataGroup.extend( - /** @lends openerp.base.StaticDataGroup# */ { +openerp.base.StaticDataGroup = openerp.base.GrouplessDataGroup.extend( /** @lends openerp.base.StaticDataGroup# */ { /** * A specialization of groupless data groups, relying on a single static * dataset as its records provider. @@ -238,19 +232,20 @@ openerp.base.StaticDataGroup = openerp.base.GrouplessDataGroup.extend( } }); -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 {openerp.base.Session} session current OpenERP session * @param {String} model the OpenERP model this dataset will manage */ - init: function(session, model, context) { - this._super(session); + init: function(source_controller, model, context) { + // we don't want the dataset to be a child of anything! + this._super(null); + this.session = source_controller ? source_controller.session : undefined; this.model = model; this.context = context || {}; this.index = null; @@ -296,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', { @@ -382,15 +381,11 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base. return this.context; } }); - openerp.base.DataSetStatic = openerp.base.DataSet.extend({ - init: function(session, model, context, ids) { - this._super(session, model, context); + init: function(parent, model, context, ids) { + this._super(parent, model, context); // all local records this.ids = ids || []; - if (this.ids.length) { - this.index = 0; - } }, read_slice: function (fields, offset, limit, callback) { var self = this; @@ -400,8 +395,10 @@ openerp.base.DataSetStatic = openerp.base.DataSet.extend({ }, set_ids: function (ids) { this.ids = ids; - this.index = this.index <= this.ids.length - 1 ? - this.index : (this.ids.length > 0 ? this.length - 1 : 0); + if (this.index !== null) { + this.index = this.index <= this.ids.length - 1 ? + this.index : (this.ids.length > 0 ? this.length - 1 : 0); + } }, unlink: function(ids) { this.on_unlink(ids); @@ -411,10 +408,9 @@ openerp.base.DataSetStatic = openerp.base.DataSet.extend({ this.set_ids(_.without.apply(null, [this.ids].concat(ids))); } }); - openerp.base.DataSetSearch = openerp.base.DataSet.extend({ - init: function(session, model, context, domain) { - this._super(session, model, context); + init: function(parent, model, context, domain) { + this._super(parent, model, context); this.domain = domain || []; this._sort = []; this.offset = 0; @@ -478,14 +474,15 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({ var self = this; return this._super(ids, function(result) { self.ids = _.without.apply(_, [self.ids].concat(ids)); - self.index = self.index <= self.ids.length - 1 ? - self.index : (self.ids.length > 0 ? self.ids.length -1 : 0); + if (this.index !== null) { + self.index = self.index <= self.ids.length - 1 ? + self.index : (self.ids.length > 0 ? self.ids.length -1 : 0); + } if (callback) callback(result); }, error_callback); } }); - openerp.base.BufferedDataSet = openerp.base.DataSetStatic.extend({ virtual_id_prefix: "one2many_v_id_", virtual_id_regex: /one2many_v_id_.*/, @@ -499,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) { @@ -590,7 +596,6 @@ openerp.base.BufferedDataSet = openerp.base.DataSetStatic.extend({ return completion.promise(); } }); - openerp.base.ReadOnlyDataSetSearch = openerp.base.DataSetSearch.extend({ create: function(data, callback, error_callback) { this.on_create(data); diff --git a/addons/base/static/src/js/data_export.js b/addons/base/static/src/js/data_export.js new file mode 100644 index 00000000000..ad440302c69 --- /dev/null +++ b/addons/base/static/src/js/data_export.js @@ -0,0 +1,402 @@ +openerp.base.data_export = function(openerp) { +openerp.base.DataExport = openerp.base.Dialog.extend({ + init: function(parent, dataset) { + this._super(parent); + this.dataset = dataset; + }, + start: function() { + var self = this; + self._super(false); + self.template = 'ExportTreeView'; + self.dialog_title = "Export Data"; + self.open({ + modal: true, + width: '55%', + height: 'auto', + position: 'top', + buttons : { + "Close" : function() { + self.close(); + }, + "Export To File" : function() { + self.on_click_export_data(); + } + }, + close: function(event, ui){ self.close();} + }); + self.on_show_exists_export_list(); + self.$element.removeClass('ui-dialog-content ui-widget-content'); + self.$element.find('#add_field').click(function() { + if ($('#field-tree-structure tr.ui-selected')) { + var fld = self.$element.find('#field-tree-structure tr.ui-selected').find('a'); + for (var i = 0; i < fld.length; i++) { + var id = $(fld[i]).attr('id').split('-')[1]; + var string = $(fld[i]).attr('string'); + self.add_field(id, string); + } + self.$element.find('#field-tree-structure tr').removeClass('ui-selected'); + } + }); + self.$element.find('#remove_field').click(function() { + self.$element.find('#fields_list option:selected').remove(); + }); + self.$element.find('#remove_all_field').click(function() { + self.$element.find('#fields_list option').remove(); + }); + self.$element.find('#export_new_list').click(function() { + self.on_show_save_list(); + }); + var import_comp = self.$element.find('#import_compat option:selected').val(), + params = { + import_compat: parseInt(import_comp) + }; + self.rpc('/base/export/get_fields', { model: self.dataset.model, params: params }, self.on_show_data); + + self.$element.find('#import_compat').change(function() { + self.$element.find('#fields_list option').remove(); + self.$element.find('#field-tree-structure').remove(); + var import_comp = self.$element.find("#import_compat option:selected").val(); + if (import_comp) { + var params = { + import_compat: parseInt(import_comp) + } + self.rpc("/base/export/get_fields", { model: self.dataset.model, params: params}, self.on_show_data); + } + }); + }, + on_show_exists_export_list: function() { + var self = this; + if (self.$element.find('#saved_export_list').is(':hidden')) { + self.$element.find('#ExistsExportList').show(); + } else { + this.rpc('/base/export/exist_export_lists', { 'model': this.dataset.model}, function(export_list) { + if (export_list.length) { + self.$element.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list})); + self.$element.find('#saved_export_list').change(function() { + self.$element.find('#fields_list option').remove(); + var export_id = self.$element.find('#saved_export_list option:selected').val(); + if (export_id) { + self.rpc('/base/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}, self.do_load_export_field); + } + }); + self.$element.find('#delete_export_list').click(function() { + var select_exp = self.$element.find('#saved_export_list option:selected'); + if (select_exp.val()) { + self.rpc('/base/export/delete_export', { export_id: parseInt(select_exp.val())}, {}); + select_exp.remove(); + if (self.$element.find('#saved_export_list option').length <= 1) { + self.$element.find('#ExistsExportList').hide(); + } + } + }); + } + }); + } + }, + do_load_export_field: function(field_list) { + var export_node = this.$element.find("#fields_list"); + for (var key in field_list) { + export_node.append(new Option(field_list[key], key)); + } + }, + on_show_save_list: function() { + var self = this; + var current_node = self.$element.find("#savenewlist"); + if (!(current_node.find("label")).length) { + current_node.append(QWeb.render('ExportNewList')); + current_node.find("#add_export_list").click(function() { + var value = current_node.find("#savelist_name").val(); + if (value) { + self.do_save_export_list(value); + } else { + alert("Pleae Enter Save Field List Name"); + } + }); + } else { + if (current_node.is(':hidden')) { + current_node.show(); + current_node.find("#savelist_name").val(""); + } else { + current_node.hide(); + } + } + }, + do_save_export_list: function(value) { + var self = this; + var export_field = self.get_fields(); + if (export_field.length) { + self.rpc("/base/export/save_export_lists", {"model": self.dataset.model, "name":value, "field_list":export_field}, function(exp_id) { + if (exp_id) { + if (self.$element.find("#saved_export_list").length > 0) { + self.$element.find("#saved_export_list").append(new Option(value, exp_id)); + } else { + self.on_show_exists_export_list(); + } + if (self.$element.find("#saved_export_list").is(":hidden")) { + self.on_show_exists_export_list(); + } + } + }); + self.on_show_save_list(); + self.$element.find("#fields_list option").remove(); + } + }, + on_click: function(id, result) { + var self = this; + self.field_id = id.split("-")[1]; + var is_loaded = 0; + _.each(result, function(record) { + if (record['id'] == self.field_id && (record['children']).length >= 1) { + var model = record['params']['model'], + prefix = record['params']['prefix'], + name = record['params']['name']; + $(record['children']).each(function(e, childid) { + if (self.$element.find("tr[id='treerow-" + childid + "']").length > 0) { + if (self.$element.find("tr[id='treerow-" + childid + "']").is(':hidden')) { + is_loaded = -1; + } else { + is_loaded++; + } + } + }); + if (is_loaded == 0) { + if (self.$element.find("tr[id='treerow-" + self.field_id +"']").find('img').attr('src') === '/base/static/src/img/expand.gif') { + if (model) { + var import_comp = self.$element.find("#import_compat option:selected").val(); + var params = { + import_compat: parseInt(import_comp), + parent_field_type : record['field_type'] + } + self.rpc("/base/export/get_fields", { + model: model, + prefix: prefix, + name: name, + field_parent : self.field_id, + params: params + }, function(results) { + self.on_show_data(results); + }); + } + } + } else if (is_loaded > 0) { + self.showcontent(self.field_id, true); + } else { + self.showcontent(self.field_id, false); + } + } + }); + }, + on_show_data: function(result) { + var self = this; + var imp_cmpt = parseInt(self.$element.find("#import_compat option:selected").val()); + var current_tr = self.$element.find("tr[id='treerow-" + self.field_id + "']"); + if (current_tr.length >= 1) { + current_tr.find('img').attr('src','/base/static/src/img/collapse.gif'); + current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result})); + } else { + self.$element.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result})); + } + _.each(result, function(record) { + if ((record.field_type == "one2many") && imp_cmpt) { + var o2m_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); + o2m_fld.addClass("oe_export_readonlyfield"); + } + if ((record.required == true) || record.required == "True") { + var required_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); + required_fld.addClass("oe_export_requiredfield"); + } + self.$element.find("img[id='parentimg-" + record.id +"']").click(function() { + self.on_click(this.id, result); + }); + + self.$element.find("tr[id='treerow-" + record.id + "']").click(function(e) { + if (e.shiftKey == true) { + var frst_click, scnd_click = ''; + if (self.row_index == 0) { + self.row_index = this.rowIndex; + frst_click = self.$element.find("tr[id^='treerow-']")[self.row_index-1]; + $(frst_click).addClass("ui-selected"); + } else { + if (this.rowIndex >=self.row_index) { + for (i = (self.row_index-1); i < this.rowIndex; i++) { + scnd_click = self.$element.find("tr[id^='treerow-']")[i]; + if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) { + $(scnd_click).addClass("ui-selected"); + } + } + } else { + for (i = (self.row_index-1); i >= (this.rowIndex-1); i--) { + scnd_click = self.$element.find("tr[id^='treerow-']")[i]; + if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) { + $(scnd_click).addClass("ui-selected"); + } + } + } + } + } + self.row_index = this.rowIndex; + + self.$element.find("tr[id='treerow-" + record.id + "']").keyup(function(e) { + self.row_index = 0; + }); + var o2m_selection = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); + if ($(o2m_selection).hasClass("oe_export_readonlyfield")) { + return false; + } + var selected = self.$element.find("tr.ui-selected"); + if ($(this).hasClass("ui-selected") && (e.ctrlKey == true)) { + $(this).find('a').blur(); + $(this).removeClass("ui-selected"); + } else if ($(this).hasClass("ui-selected") && (e.ctrlKey == false) && (e.shiftKey == false)) { + selected.find('a').blur(); + selected.removeClass("ui-selected"); + $(this).find('a').focus(); + $(this).addClass("ui-selected"); + } else if (!$(this).hasClass("ui-selected") && (e.ctrlKey == false) && (e.shiftKey == false)) { + selected.find('a').blur(); + selected.removeClass("ui-selected"); + $(this).find('a').focus(); + $(this).addClass("ui-selected"); + } else if (!$(this).hasClass("ui-selected") && (e.ctrlKey == true)) { + $(this).find('a').focus(); + $(this).addClass("ui-selected"); + } + return false; + }); + + self.$element.find("tr[id='treerow-" + record.id + "']").keydown(function(e) { + var keyCode = e.keyCode || e.which; + arrow = {left: 37, up: 38, right: 39, down: 40 }; + switch (keyCode) { + case arrow.left: + if ($(this).find('img').attr('src') === '/base/static/src/img/collapse.gif') { + self.on_click(this.id, result); + } + break; + case arrow.up: + var elem = this; + $(elem).removeClass("ui-selected"); + while ($(elem).prev().is(":visible") == false) { + elem = $(elem).prev(); + } + if (!$(elem).prev().find('#tree-column').hasClass("oe_export_readonlyfield")) { + $(elem).prev().addClass("ui-selected"); + } + $(elem).prev().find('a').focus(); + break; + case arrow.right: + if ($(this).find('img').attr('src') == '/base/static/src/img/expand.gif') { + self.on_click(this.id, result); + } + break; + case arrow.down: + var elem = this; + $(elem).removeClass("ui-selected"); + while($(elem).next().is(":visible") == false) { + elem = $(elem).next(); + } + if (!$(elem).next().find('#tree-column').hasClass("oe_export_readonlyfield")) { + $(elem).next().addClass("ui-selected"); + } + $(elem).next().find('a').focus(); + break; + } + }); + self.$element.find("tr[id='treerow-" + record.id + "']").dblclick(function(e) { + var $o2m_selection = self.$element.find("tr[id^='treerow-" + record.id + "']").find('#tree-column'); + if (!$o2m_selection.hasClass("oe_export_readonlyfield")) { + var field_id = $(this).find("a").attr("id"); + if (field_id) { + self.add_field(field_id.split('-')[1], $(this).find("a").attr("string")); + } + } + }); + }); + self.$element.find('#fields_list').mouseover(function(event) { + if (event.relatedTarget) { + if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) { + var field_id = event.relatedTarget.attributes["id"]["value"]; + if (field_id && field_id.split("-")[0] === 'export') { + if (!self.$element.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) { + self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]); + } + } + } + } + }); + }, + showcontent: function(id, flag) { + // show & hide the contents + var first_child = this.$element.find("tr[id='treerow-" + id + "']").find('img'); + if (flag) { + first_child.attr('src', '/base/static/src/img/expand.gif'); + } + else { + first_child.attr('src', '/base/static/src/img/collapse.gif'); + } + var child_field = this.$element.find("tr[id^='treerow-" + id +"/']"); + var child_len = (id.split("/")).length + 1; + for (var i = 0; i < child_field.length; i++) { + if (flag) { + $(child_field[i]).hide(); + } else { + if (child_len == (child_field[i].id.split("/")).length) { + if ($(child_field[i]).find('img').attr('src') == '/base/static/src/img/collapse.gif') { + $(child_field[i]).find('img').attr('src', '/base/static/src/img/expand.gif'); + } + $(child_field[i]).show(); + } + } + } + }, + add_field: function(field_id, string) { + var field_list = this.$element.find('#fields_list'); + if (this.$element.find("#fields_list option[value='" + field_id + "']") && !this.$element.find("#fields_list option[value='" + field_id + "']").length) { + field_list.append(new Option(string, field_id)); + } + }, + get_fields: function() { + var export_field = []; + this.$element.find("#fields_list option").each(function() { + export_field.push($(this).val()); + }); + if (!export_field.length) { + alert('Please select fields to save export list...'); + } + return export_field; + }, + on_click_export_data: function() { + var self = this; + var export_field = {}; + var flag = true; + self.$element.find("#fields_list option").each(function() { + export_field[$(this).val()] = $(this).text(); + flag = false; + }); + if (flag) { + alert('Please select fields to export...'); + return; + } + + var import_comp = self.$element.find("#import_compat option:selected").val(), + export_format = self.$element.find("#export_format").val(); + + self.rpc("/base/export/export_data", { + model: self.dataset.model, + fields: export_field, + ids: self.dataset.ids, + domain: self.dataset.domain, + import_compat: parseInt(import_comp), + export_format: export_format + }, function(data) { + window.location = "data:text/csv/excel;charset=utf8," + data; + self.close(); + }); + }, + close: function() { + $(this.$dialog).remove(); + this._super(); + } +}); + +}; diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index 19f5e271858..f8e2e91667d 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -1,7 +1,7 @@ openerp.base.form = function (openerp) { openerp.base.views.add('form', 'openerp.base.FormView'); -openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{ +openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{ /** * Indicates that this view is not searchable, and thus that no search * view should be displayed (if there is one active). @@ -17,9 +17,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV * * @property {openerp.base.Registry} registry=openerp.base.form.widgets widgets registry for this form view instance */ - init: function(view_manager, session, element_id, dataset, view_id) { - this._super(session, element_id); - this.view_manager = view_manager || new openerp.base.NullViewManager(); + init: function(parent, element_id, dataset, view_id, options) { + this._super(parent, element_id); + this.set_default_options(); this.dataset = dataset; this.model = dataset.model; this.view_id = view_id; @@ -30,13 +30,14 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV this.datarecord = {}; this.ready = false; this.show_invalid = true; - this.touched = false; - this.flags = this.view_manager.flags || {}; + this.dirty = false; this.default_focus_field = null; this.default_focus_button = null; this.registry = openerp.base.form.widgets; this.has_been_loaded = $.Deferred(); this.$form_header = null; + this.options = options || {}; + _.defaults(this.options, {"always_show_new_button": true}); }, start: function() { //this.log('Starting FormView '+this.model+this.view_id) @@ -47,14 +48,15 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV return def.promise(); } else { var context = new openerp.base.CompoundContext(this.dataset.get_context()); - if (this.view_manager.action && this.view_manager.action.context) { - context.add(this.view_manager.action.context); - } return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id, - toolbar:!!this.flags.sidebar, context: context}, this.on_loaded); + toolbar: this.options.sidebar, context: context}, this.on_loaded); } }, stop: function() { + if (this.sidebar) { + this.sidebar.attachments.stop(); + this.sidebar.stop(); + } _.each(this.widgets, function(w) { w.stop(); }); @@ -83,22 +85,35 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV $('' + openerp.base.json_node_to_xml(self.fields_view.arch, true) + '').dialog({ width: '95%', height: 600}); }); - this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar); + if (this.options.sidebar && this.options.sidebar_id) { + this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id); + this.sidebar.start(); + this.sidebar.do_unfold(); + this.sidebar.attachments = new openerp.base.form.SidebarAttachments(this.sidebar, this.sidebar.add_section('attachments', "Attachments"), this); + this.sidebar.add_toolbar(data.fields_view.toolbar); + this.set_common_sidebar_sections(this.sidebar); + } this.has_been_loaded.resolve(); }, do_show: function () { - var self = this; + var promise; if (this.dataset.index === null) { // null index means we should start a new record - this.on_button_new(); + promise = this.on_button_new(); } else { - this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); + promise = this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); } - self.$element.show(); - this.view_manager.sidebar.do_refresh(true); + this.$element.show(); + if (this.sidebar) { + this.sidebar.$element.show(); + } + return promise; }, do_hide: function () { this.$element.hide(); + if (this.sidebar) { + this.sidebar.$element.hide(); + } }, on_record_loaded: function(record) { if (!record) { @@ -107,28 +122,30 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV if (!record.id) { this.$form_header.find('.oe_form_on_create').show(); this.$form_header.find('.oe_form_on_update').hide(); - this.$form_header.find('button.oe_form_button_new').hide(); + if (!this.options["always_show_new_button"]) { + this.$form_header.find('button.oe_form_button_new').hide(); + } } else { this.$form_header.find('.oe_form_on_create').hide(); 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); } } @@ -136,7 +153,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV this.on_form_changed(); this.show_invalid = this.ready = true; this.do_update_pager(record.id == null); - this.do_update_sidebar(); + if (this.sidebar) { + this.sidebar.attachments.do_update(); + } if (this.default_focus_field) { this.default_focus_field.focus(); } @@ -245,7 +264,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV processed.push(field.name); if (field.get_value() != value) { field.set_value(value); - field.touched = true; + field.dirty = true; if (_.indexOf(processed, field.name) < 0) { this.do_onchange(field, processed); } @@ -271,10 +290,14 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV }, on_button_new: function() { var self = this; + var def = $.Deferred(); $.when(this.has_been_loaded).then(function() { self.dataset.default_get( - _.keys(self.fields_view.fields), self.on_record_loaded); + _.keys(self.fields_view.fields)).then(self.on_record_loaded).then(function() { + def.resolve(); + }); }); + return def.promise(); }, /** * Triggers saving the form's record. Chooses between creating a new @@ -289,37 +312,40 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV if (!this.ready) { return false; } - var invalid = false, + var form_dirty = false, + form_invalid = false, values = {}, first_invalid_field = null; for (var f in this.fields) { f = this.fields[f]; - if (f.invalid) { - invalid = true; + if (!f.is_valid()) { + form_invalid = true; f.update_dom(); if (!first_invalid_field) { first_invalid_field = f; } - } else if (f.touched) { + } else if (f.is_dirty()) { + form_dirty = true; values[f.name] = f.get_value(); } } - if (invalid) { + if (form_invalid) { first_invalid_field.focus(); this.on_invalid(); return false; - } else { + } else if (form_dirty) { this.log("About to save", values); if (!this.datarecord.id) { - this.dataset.create(values, function(r) { + return this.dataset.create(values, function(r) { self.on_created(r, success, prepend_on_create); }); } else { - this.dataset.write(this.datarecord.id, values, function(r) { + return this.dataset.write(this.datarecord.id, values, function(r) { self.on_saved(r, success); }); } - return true; + } else { + return false; } }, do_save_edit: function() { @@ -333,7 +359,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV on_invalid: function() { var msg = "
    "; _.each(this.fields, function(f) { - if (f.invalid) { + if (!f.is_valid()) { msg += "
  • " + f.string + "
  • "; } }); @@ -377,7 +403,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV this.dataset.index = 0; } this.do_update_pager(); - this.do_update_sidebar(); + if (this.sidebar) { + this.sidebar.attachments.do_update(); + } this.notification.notify("Record created", "The record has been created with id #" + this.datarecord.id); if (success) { success(_.extend(r, {created: true})); @@ -394,52 +422,6 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV do_cancel: function () { this.notification.notify("Cancelling form"); }, - do_update_sidebar: function() { - if (this.flags.sidebar === false) { - return; - } - if (!this.datarecord.id) { - this.on_attachments_loaded([]); - } else { - // TODO fme: modify this so it doesn't try to load attachments when there is not sidebar - /*(new openerp.base.DataSetSearch( - this.session, 'ir.attachment', this.dataset.get_context(), - [['res_model', '=', this.dataset.model], - ['res_id', '=', this.datarecord.id], - ['type', 'in', ['binary', 'url']]])).read_slice( - ['name', 'url', 'type'], false, false, - this.on_attachments_loaded);*/ - } - }, - on_attachments_loaded: function(attachments) { - this.$sidebar = this.view_manager.sidebar.$element.find('.sidebar-attachments'); - this.attachments = attachments; - this.$sidebar.html(QWeb.render('FormView.sidebar.attachments', this)); - this.$sidebar.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete); - this.$sidebar.find('.oe-binary-file').change(this.on_attachment_changed); - }, - on_attachment_changed: function(e) { - window[this.element_id + '_iframe'] = this.do_update_sidebar; - var $e = $(e.target); - if ($e.val() != '') { - this.$sidebar.find('form.oe-binary-form').submit(); - $e.parent().find('input[type=file]').attr('disabled', 'true'); - $e.parent().find('button').attr('disabled', 'true').find('img, span').toggle(); - } - }, - on_attachment_delete: function(e) { - var self = this, $e = $(e.currentTarget); - var name = _.trim($e.parent().find('a.oe-sidebar-attachments-link').text()); - if (confirm("Do you really want to delete the attachment " + name + " ?")) { - this.rpc('/base/dataset/unlink', { - model: 'ir.attachment', - ids: [parseInt($e.attr('data-id'))] - }, function(r) { - $e.parent().remove(); - self.notification.notify("Delete an attachment", "The attachment '" + name + "' has been deleted"); - }); - } - }, reload: function() { if (this.datarecord.id) { this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); @@ -460,6 +442,54 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV /** @namespace */ openerp.base.form = {}; +openerp.base.form.SidebarAttachments = openerp.base.Widget.extend({ + init: function(parent, element_id, form_view) { + this._super(parent, element_id); + this.view = form_view; + }, + do_update: function() { + if (!this.view.datarecord.id) { + this.on_attachments_loaded([]); + } else { + (new openerp.base.DataSetSearch( + this, 'ir.attachment', this.view.dataset.get_context(), + [['res_model', '=', this.view.dataset.model], + ['res_id', '=', this.view.datarecord.id], + ['type', 'in', ['binary', 'url']]])).read_slice( + ['name', 'url', 'type'], false, false, + this.on_attachments_loaded); + } + }, + on_attachments_loaded: function(attachments) { + this.attachments = attachments; + this.$element.html(QWeb.render('FormView.sidebar.attachments', this)); + this.$element.find('.oe-binary-file').change(this.on_attachment_changed); + this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete); + }, + on_attachment_changed: function(e) { + window[this.element_id + '_iframe'] = this.do_update; + var $e = $(e.target); + if ($e.val() != '') { + this.$element.find('form.oe-binary-form').submit(); + $e.parent().find('input[type=file]').attr('disabled', 'true'); + $e.parent().find('button').attr('disabled', 'true').find('img, span').toggle(); + } + }, + on_attachment_delete: function(e) { + var self = this, $e = $(e.currentTarget); + var name = _.trim($e.parent().find('a.oe-sidebar-attachments-link').text()); + if (confirm("Do you really want to delete the attachment " + name + " ?")) { + this.rpc('/base/dataset/unlink', { + model: 'ir.attachment', + ids: [parseInt($e.attr('data-id'))] + }, function(r) { + $e.parent().remove(); + self.notification.notify("Delete an attachment", "The attachment '" + name + "' has been deleted"); + }); + } + } +}); + openerp.base.form.compute_domain = function(expr, fields) { var stack = []; for (var i = expr.length - 1; i >= 0; i--) { @@ -519,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; @@ -529,7 +559,7 @@ openerp.base.form.Widget = openerp.base.Controller.extend({ this.element_name = this.element_name || this.type; this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_"); - this._super(this.view.session, this.element_id); + this._super(view, this.element_id); this.view.widgets[this.element_id] = this; this.children = node.children; @@ -685,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; @@ -696,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); }); @@ -741,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') { @@ -776,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; @@ -789,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(); @@ -901,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; @@ -942,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); @@ -962,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); @@ -1049,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); @@ -1256,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; } @@ -1289,7 +1331,7 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ if (!self.value) { return; } - var pop = new openerp.base.form.FormOpenPopup(null, self.view.session); + var pop = new openerp.base.form.FormOpenPopup(self.view); pop.show_element(self.field.relation, self.value[0],self.build_context(), {}); pop.on_write_completed.add_last(function() { self.set_value(self.value[0]); @@ -1377,7 +1419,7 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ var search_val = request.term; var self = this; - var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, self.build_context()); + var dataset = new openerp.base.DataSetStatic(this, this.field.relation, self.build_context()); dataset.name_search(search_val, self.build_domain(), 'ilike', this.limit + 1, function(data) { @@ -1419,7 +1461,7 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ }, _quick_create: function(name) { var self = this; - var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, self.build_context()); + var dataset = new openerp.base.DataSetStatic(this, this.field.relation, self.build_context()); dataset.name_create(name, function(data) { self._change_int_ext_value(data); }).fail(function(error, event) { @@ -1431,7 +1473,7 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ // all search/create popup handling _search_create_popup: function(view, ids, context) { var self = this; - var pop = new openerp.base.form.SelectCreatePopup(null, self.view.session); + var pop = new openerp.base.form.SelectCreatePopup(this); pop.select_element(self.field.relation,{ initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined, initial_view: view, @@ -1439,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.session, 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(); @@ -1477,7 +1519,7 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ self._change_int_ext_value(rval); }; if(typeof(value) === "number") { - var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, self.build_context()); + var dataset = new openerp.base.DataSetStatic(this, this.field.relation, self.build_context()); dataset.name_get([value], function(data) { real_set_value(data[0]); }).fail(function() {self.tmp_value = undefined;}); @@ -1555,13 +1597,15 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ this._super(view, node); this.template = "FieldOne2Many"; this.is_started = $.Deferred(); + this.form_last_update = $.Deferred(); + this.disable_utility_classes = true; }, start: function() { this._super.apply(this, arguments); var self = this; - this.dataset = new openerp.base.form.One2ManyDataSet(this.session, this.field.relation); + this.dataset = new openerp.base.form.One2ManyDataSet(this, this.field.relation); this.dataset.o2m = this; this.dataset.parent_view = this.view; this.dataset.on_change.add_last(function() { @@ -1585,20 +1629,31 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ }); this.views = views; - this.viewmanager = new openerp.base.ViewManager(this.view.session, - this.element_id, this.dataset, views); + this.viewmanager = new openerp.base.ViewManager(this, this.element_id, this.dataset, views); this.viewmanager.registry = openerp.base.views.clone({ - list: 'openerp.base.form.One2ManyListView' + list: 'openerp.base.form.One2ManyListView', + form: 'openerp.base.form.One2ManyFormView' + }); + var once = $.Deferred().then(function() { + self.form_last_update.resolve(); }); - this.viewmanager.on_controller_inited.add_last(function(view_type, controller) { if (view_type == "list") { controller.o2m = self; } else if (view_type == "form") { - // TODO niv + 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); @@ -1609,7 +1664,12 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ if(self.viewmanager.active_view === "list") { view.reload_content(); } else if (self.viewmanager.active_view === "form") { - // TODO niv: implement + if (this.dataset.index === null && this.dataset.ids.length >= 1) { + this.dataset.index = 0; + } + this.form_last_update.then(function() { + this.form_last_update = view.do_show(); + }); } }, set_value_from_ui: function() {}, @@ -1663,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(); }); @@ -1687,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); } }); @@ -1700,13 +1804,17 @@ openerp.base.form.One2ManyDataSet = openerp.base.BufferedDataSet.extend({ } }); +openerp.base.form.One2ManyFormView = openerp.base.FormView.extend({ + +}); + openerp.base.form.One2ManyListView = openerp.base.ListView.extend({ do_add_record: function () { if (this.options.editable) { this._super.apply(this, arguments); } else { var self = this; - var pop = new openerp.base.form.SelectCreatePopup(null, self.o2m.view.session); + var pop = new openerp.base.form.SelectCreatePopup(this); pop.select_element(self.o2m.field.relation,{ initial_view: "form", alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined, @@ -1725,7 +1833,7 @@ openerp.base.form.One2ManyListView = openerp.base.ListView.extend({ }, do_activate_record: function(index, id) { var self = this; - var pop = new openerp.base.form.FormOpenPopup(null, self.o2m.view.session); + var pop = new openerp.base.form.FormOpenPopup(self.o2m.view); pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(),{ auto_write: false, alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined, @@ -1752,15 +1860,13 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({ var self = this; - this.dataset = new openerp.base.form.Many2ManyDataSet( - this.session, 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(); }); - this.list_view = new openerp.base.form.Many2ManyListView( - null, this.view.session, this.list_id, this.dataset, false, { + this.list_view = new openerp.base.form.Many2ManyListView(new openerp.base.NullViewManager(this), this.list_id, this.dataset, false, { 'addable': 'Add', 'selectable': self.multi_selection }); @@ -1803,8 +1909,7 @@ openerp.base.form.Many2ManyDataSet = openerp.base.DataSetStatic.extend({ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({ do_add_record: function () { - var pop = new openerp.base.form.SelectCreatePopup( - null, this.m2m_field.view.session); + var pop = new openerp.base.form.SelectCreatePopup(this); pop.select_element(this.model, {}, new openerp.base.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]), this.m2m_field.build_context()); @@ -1822,7 +1927,7 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({ }, do_activate_record: function(index, id) { var self = this; - var pop = new openerp.base.form.FormOpenPopup(null, this.m2m_field.view.session); + var pop = new openerp.base.form.FormOpenPopup(this); pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {}); pop.on_write_completed.add_last(function() { self.reload_content(); @@ -1830,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", /** @@ -1853,7 +1958,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({ }, start: function() { this._super(); - this.dataset = new openerp.base.ReadOnlyDataSetSearch(this.session, this.model, + this.dataset = new openerp.base.ReadOnlyDataSetSearch(this, this.model, this.context); this.dataset.parent_view = this.options.parent_view; if (this.options.initial_view == "search") { @@ -1867,7 +1972,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({ if (this.searchview) { this.searchview.stop(); } - this.searchview = new openerp.base.SearchView(null, this.session, + this.searchview = new openerp.base.SearchView(this, this.element_id + "_search", this.dataset, false, { "selectable": !this.options.disable_multiple_selection, "deletable": false @@ -1895,7 +2000,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({ $sbutton.click(function() { self.on_select_elements(self.selected_ids); }); - self.view_list = new openerp.base.form.SelectCreateListView( null, self.session, + self.view_list = new openerp.base.form.SelectCreateListView(self, self.element_id + "_view_list", self.dataset, false, {'deletable': false}); self.view_list.popup = self; @@ -1910,7 +2015,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({ if (!this.options.auto_create) return; var self = this; - var wdataset = new openerp.base.DataSetSearch(this.session, this.model, this.context, this.domain); + var wdataset = new openerp.base.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; wdataset.create(data, function(r) { self.on_select_elements([r.result]); @@ -1935,8 +2040,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({ this.view_list.$element.hide(); } this.dataset.index = null; - this.view_form = new openerp.base.FormView(null, this.session, - this.element_id + "_view_form", this.dataset, false); + this.view_form = new openerp.base.FormView(this, this.element_id + "_view_form", this.dataset, false); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } @@ -1971,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", /** @@ -1993,8 +2097,7 @@ openerp.base.form.FormOpenPopup = openerp.base.BaseWidget.extend({ }, start: function() { this._super(); - this.dataset = new openerp.base.ReadOnlyDataSetSearch(this.session, this.model, - this.context); + this.dataset = new openerp.base.ReadOnlyDataSetSearch(this, this.model, this.context); this.dataset.ids = [this.row_id]; this.dataset.index = 0; this.dataset.parent_view = this.options.parent_view; @@ -2005,7 +2108,7 @@ openerp.base.form.FormOpenPopup = openerp.base.BaseWidget.extend({ if (!this.options.auto_write) return; var self = this; - var wdataset = new openerp.base.DataSetSearch(this.session, this.model, this.context, this.domain); + var wdataset = new openerp.base.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; wdataset.write(id, data, function(r) { self.on_write_completed(); @@ -2014,8 +2117,7 @@ openerp.base.form.FormOpenPopup = openerp.base.BaseWidget.extend({ on_write_completed: function() {}, setup_form_view: function() { var self = this; - this.view_form = new openerp.base.FormView(null, this.session, - this.element_id + "_view_form", this.dataset, false); + this.view_form = new openerp.base.FormView(this, this.element_id + "_view_form", this.dataset, false); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } @@ -2199,8 +2301,10 @@ openerp.base.form.widgets = new openerp.base.Registry({ 'url' : 'openerp.base.form.FieldUrl', 'text' : 'openerp.base.form.FieldText', 'text_wiki' : 'openerp.base.form.FieldText', - 'date' : 'openerp.base.form.FieldDate', - 'datetime' : 'openerp.base.form.FieldDatetime', +// 'date' : 'openerp.base.form.FieldDate', +// 'datetime' : 'openerp.base.form.FieldDatetime', + 'date' : 'openerp.base.form.FieldChar', + 'datetime' : 'openerp.base.form.FieldChar', 'selection' : 'openerp.base.form.FieldSelection', 'many2one' : 'openerp.base.form.FieldMany2One', 'many2many' : 'openerp.base.form.FieldMany2Many', diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 49cd10c10b6..d64d83329c0 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -24,24 +24,37 @@ openerp.base.list = { ].join('') } - var record = row_data[column.id]; - if (record.value === false) { - return value_if_empty === undefined ? '' : value_if_empty; + var value = row_data[column.id].value; + + // If NaN value, display as with a `false` (empty cell) + if (typeof value === 'number' && isNaN(value)) { + value = false; + } + switch (value) { + case false: + case Infinity: + case -Infinity: + return value_if_empty === undefined ? '' : value_if_empty; } switch (column.widget || column.type) { - case 'many2one': - // name_get value format - return record.value[1]; + case 'integer': + return _.sprintf('%d', value); + case 'float': + var precision = column.digits ? column.digits[1] : 2; + return _.sprintf('%.' + precision + 'f', value); case 'float_time': return _.sprintf("%02d:%02d", - Math.floor(record.value), - Math.round((record.value % 1) * 60)); + Math.floor(value), + Math.round((value % 1) * 60)); case 'progressbar': return _.sprintf( '%.2f%%', - record.value, record.value); + value, value); + case 'many2one': + // name_get value format + return value[1]; default: - return record.value; + return value; } } }; @@ -87,9 +100,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * * @borrows openerp.base.ActionExecutor#execute_action as #execute_action */ - init: function(view_manager, session, element_id, dataset, view_id, options) { - this._super(session, element_id); - this.view_manager = view_manager || new openerp.base.NullViewManager(); + 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; @@ -97,10 +111,9 @@ 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)); - + if (this.dataset instanceof openerp.base.DataSetStatic) { this.groups.datagroup = new openerp.base.StaticDataGroup(this.dataset); } @@ -254,8 +267,12 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi }) .val(self._limit || 'NaN'); }); - - this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar); + if (this.options.sidebar && this.options.sidebar_id) { + this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id); + this.sidebar.start(); + this.sidebar.add_toolbar(data.fields_view.toolbar); + this.set_common_sidebar_sections(this.sidebar); + } }, /** * Configures the ListView pager based on the provided dataset's information @@ -347,16 +364,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi } var aggregation_func = column['group_operator'] || 'sum'; - if (!column[aggregation_func]) { - return {}; - } - - return { - field: column.id, - type: column.type, + return _.extend({}, column, { 'function': aggregation_func, label: column[aggregation_func] - }; + }); }); }, /** @@ -385,15 +396,20 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi }, do_show: function () { this.$element.show(); + if (this.sidebar) { + this.sidebar.$element.show(); + } if (this.hidden) { - this.$element.find('table').append( + this.$element.find('.oe-listview-content').append( this.groups.apoptosis().render()); this.hidden = false; } - this.view_manager.sidebar.do_refresh(true); }, do_hide: function () { this.$element.hide(); + if (this.sidebar) { + this.sidebar.$element.hide(); + } this.hidden = true; }, /** @@ -413,7 +429,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi model: this.model, view_id: this.view_id, context: this.dataset.get_context(), - toolbar: !!this.flags.sidebar + toolbar: this.options.sidebar }, callback); } }, @@ -421,7 +437,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * re-renders the content of the list view */ reload_content: function () { - this.$element.find('table').append( + this.$element.find('.oe-listview-content').append( this.groups.apoptosis().render( $.proxy(this, 'compute_aggregates'))); }, @@ -451,7 +467,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi this.dataset.context = results.context; this.dataset.domain = results.domain; this.groups.datagroup = new openerp.base.DataGroup( - this.session, this.model, + this, this.model, results.domain, results.context, results.group_by); this.groups.datagroup.sort = this.dataset._sort; @@ -475,6 +491,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi var self = this; return $.when(this.dataset.unlink(ids)).then(function () { self.groups.drop_records(ids); + self.compute_aggregates(); }); }, /** @@ -503,11 +520,17 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @param {Function} callback should be called after the action is executed, if non-null */ do_action: function (name, id, callback) { - var action = _.detect(this.columns, function (field) { + var self = this, + action = _.detect(this.columns, function (field) { return field.name === name; }); if (!action) { return; } - this.execute_action(action, this.dataset, this.session.action_manager, id, callback); + this.execute_action( + action, this.dataset, this.session.action_manager, id, function () { + $.when(callback.apply(this, arguments).then(function () { + self.compute_aggregates(); + })); + }); }, /** * Handles the activation of a record (clicking on it) @@ -554,7 +577,6 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi compute_aggregates: function (records) { var columns = _(this.aggregate_columns).filter(function (column) { return column['function']; }); - if (_.isEmpty(columns)) { return; } if (_.isEmpty(records)) { @@ -562,17 +584,39 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi } var count = 0, sums = {}; - _(columns).each(function (column) { sums[column.field] = 0; }); + _(columns).each(function (column) { + switch (column['function']) { + case 'max': + sums[column.id] = -Infinity; + break; + case 'min': + sums[column.id] = Infinity; + break; + default: + sums[column.id] = 0; + } + }); _(records).each(function (record) { count += record.count || 1; _(columns).each(function (column) { - var field = column.field; + var field = column.id, + value = record.values[field]; switch (column['function']) { case 'sum': - sums[field] += record.values[field]; + sums[field] += value; break; case 'avg': - sums[field] += record.count * record.values[field]; + sums[field] += record.count * value; + break; + case 'min': + if (sums[field] > value) { + sums[field] = value; + } + break; + case 'max': + if (sums[field] < value) { + sums[field] = value; + } break; } }); @@ -580,14 +624,13 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi var aggregates = {}; _(columns).each(function (column) { - var field = column.field; + var field = column.id; switch (column['function']) { - case 'sum': - aggregates[field] = sums[field]; - break; case 'avg': - aggregates[field] = sums[field] / count; + aggregates[field] = {value: sums[field] / count}; break; + default: + aggregates[field] = {value: sums[field]}; } }); @@ -599,14 +642,14 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi if (!column['function']) { return; } - var pattern = (column.type == 'integer') ? '%d' : '%.2f'; - $footer_cells.filter(_.sprintf('[data-field=%s]', column.field)) - .text(_.sprintf(pattern, aggregation[column.field])); + + $footer_cells.filter(_.sprintf('[data-field=%s]', column.id)) + .html(openerp.base.list.render_cell(aggregation, column)); }); } // TODO: implement reorder (drag and drop rows) }); -openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List# */{ +openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.ListView.List# */{ /** * List display for the ListView, handles basic DOM events and transforms * them in the relevant higher-level events, to which the list view (or @@ -666,7 +709,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List index = self.row_position($row); $(self).trigger('action', [field, record_id, function () { - self.reload_record(index, true); + return self.reload_record(index, true); }]); }) .delegate('tr', 'click', function (e) { @@ -804,7 +847,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List return $.when(read_p).then(function () { self.$current.children().eq(record_index) - .replaceWith(self.render_record(record_index)); }) + .replaceWith(self.render_record(record_index)); }); }, /** * Renders a list record to HTML @@ -842,7 +885,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List } // drag and drop }); -openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Groups# */{ +openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.base.ListView.Groups# */{ passtrough_events: 'action deleted row_link', /** * Grouped display for the ListView. Handles basic DOM events and interacts @@ -979,8 +1022,8 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr row_data[group.grouped_on] = group; var group_column = _(self.columns).detect(function (column) { return column.id === group.grouped_on; }); - $group_column.text(openerp.base.list.render_cell( - row_data, group_column, "Undefined" + $group_column.html(openerp.base.list.render_cell( + row_data, group_column, "Undefined" )); if (group.openable) { // Make openable if not terminal group & group_by_no_leaf @@ -1084,6 +1127,45 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr }); return d.promise(); }, + setup_resequence_rows: function (list, dataset) { + // drag and drop enabled if list is not sorted and there is a + // "sequence" column in the view. + if ((dataset.sort && dataset.sort()) + || !_(this.columns).any(function (column) { + return column.name === 'sequence'; })) { + return; + } + // ondrop, move relevant record & fix sequences + list.$current.sortable({ + stop: function (event, ui) { + var from = ui.item.data('index'), + to = ui.item.prev().data('index') || 0; + if (from === to) { return; } + list.rows.splice(to, 0, list.rows.splice(from, 1)[0]); + + ui.item.parent().children().each(function (i, e) { + // reset record-index accelerators on rows and even/odd + var even = i%2 === 0; + $(e).data('index', i) + .toggleClass('even', even) + .toggleClass('odd', !even); + }); + + // resequencing time! + var data, index = to, + // if drag to 1st row (to = 0), start sequencing from 0 + // (exclusive lower bound) + seq = to ? list.rows[to - 1].data.sequence.value : 0; + while (++seq, data = list.rows[index++].data) { + data.sequence.value = seq; + // write are independent from one another, so we can just + // launch them all at the same time and we don't really + // give a fig about when they're done + dataset.write(data.id.value, {sequence: seq}); + } + } + }); + }, render: function (post_render) { var self = this; var $element = $(''); @@ -1102,6 +1184,7 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr self.children[null] = list; self.elements = [list.$current.replaceAll($element)[0]]; + self.setup_resequence_rows(list, dataset); if (post_render) { post_render(); } }); }); diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js index 1b986e3d2fb..763746bb0bf 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -1,9 +1,9 @@ openerp.base.search = function(openerp) { -openerp.base.SearchView = openerp.base.Controller.extend({ - init: function(view_manager, session, element_id, dataset, view_id, defaults) { - this._super(session, element_id); - this.view_manager = view_manager || new openerp.base.NullViewManager(); +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(); this.dataset = dataset; this.model = dataset.model; this.view_id = view_id; @@ -14,10 +14,13 @@ openerp.base.SearchView = openerp.base.Controller.extend({ this.enabled_filters = []; this.has_focus = false; + + this.ready = $.Deferred(); }, start: function() { //this.log('Starting SearchView '+this.model+this.view_id) - return this.rpc("/base/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); + this.rpc("/base/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); + return this.ready.promise(); }, show: function () { this.$element.show(); @@ -107,12 +110,13 @@ openerp.base.SearchView = openerp.base.Controller.extend({ } }, on_loaded: function(data) { - var lines = this.make_widgets( - data.fields_view['arch'].children, - data.fields_view.fields); + var self = this, + lines = this.make_widgets( + data.fields_view['arch'].children, + data.fields_view.fields); // for extended search view - var ext = new openerp.base.search.ExtendedSearch(null, this.session, this.model); + var ext = new openerp.base.search.ExtendedSearch(this, this.model); lines.push([ext]); this.inputs.push(ext); @@ -121,6 +125,7 @@ openerp.base.SearchView = openerp.base.Controller.extend({ 'lines': lines, 'defaults': this.defaults }); + // We don't understand why the following commented line does not work in Chrome but // the non-commented line does. As far as we investigated, only God knows. //this.$element.html(render); @@ -132,30 +137,90 @@ openerp.base.SearchView = openerp.base.Controller.extend({ .submit(this.do_search) .bind('reset', this.do_clear); // start() all the widgets - _(lines).chain().flatten().each(function (widget) { - widget.start(); + var widget_starts = _(lines).chain().flatten().map(function (widget) { + return widget.start(); + }).value(); + + $.when.apply(null, widget_starts).then(function () { + self.ready.resolve(); }); - // filters management - this.$element.find(".oe_search-view-filters-management").change(this.on_filters_management); + this.reload_managed_filters(); + }, + reload_managed_filters: function() { + var self = this; + return this.rpc('/base/searchview/get_filters', { + model: this.dataset.model + }).then(function(result) { + self.managed_filters = result; + var filters = self.$element.find(".oe_search-view-filters-management"); + filters.html(QWeb.render("SearchView.managed-filters", {filters: result})); + filters.change(self.on_filters_management); + }); }, /** * Handle event when the user make a selection in the filters management select box. */ on_filters_management: function(e) { - var select = this.$element.find(".oe_search-view-filters-management"), - val = select.val(); - select.val("_filters"); + var self = this; + var select = this.$element.find(".oe_search-view-filters-management"); + var val = select.val(); - if (val.slice(0,1) == "_") // useless action + if (val.slice(0,1) == "_") { // useless action + select.val("_filters"); return; + } if (val.slice(0, "get:".length) == "get:") { val = val.slice("get:".length); - //TODO niv + val = parseInt(val); + var filter = this.managed_filters[val]; + this.on_search([filter.domain], [filter.context], []); } else if (val == "save_filter") { - //TODO niv + select.val("_filters"); + var data = this.build_search_data(); + var context = new openerp.base.CompoundContext(); + _.each(data.contexts, function(x) { + context.add(x); + }); + var domain = new openerp.base.CompoundDomain(); + _.each(data.domains, function(x) { + domain.add(x); + }); + var dial_html = QWeb.render("SearchView.managed-filters.add"); + var $dial = $(dial_html); + $dial.dialog({ + modal: true, + title: "Filter Entry", + buttons: { + Cancel: function() { + $(this).dialog("close"); + }, + OK: function() { + $(this).dialog("close"); + var name = $(this).find("input").val(); + self.rpc('/base/searchview/save_filter', { + model: self.dataset.model, + context_to_save: context, + domain: domain, + name: name + }).then(function(result) { + self.reload_managed_filters(); + }); + }, + } + }); } else { // manage_filters - //TODO niv + select.val("_filters"); + this.do_action({ + res_model: 'ir.filters', + views: [[false, 'list'], [false, 'form']], + type: 'ir.actions.act_window', + context: {"search_default_user_id": this.session.uid, + "search_default_model_id": this.dataset.model}, + target: "current", + limit : 80, + auto_search : true + }); } }, /** @@ -170,8 +235,22 @@ openerp.base.SearchView = openerp.base.Controller.extend({ * @param e jQuery event object coming from the "Search" button */ do_search: function (e) { + // reset filters management + var select = this.$element.find(".oe_search-view-filters-management"); + select.val("_filters"); + if (e && e.preventDefault) { e.preventDefault(); } + var data = this.build_search_data(); + + if (data.errors.length) { + this.on_invalid(data.errors); + return; + } + + this.on_search(data.domains, data.contexts, data.groupbys); + }, + build_search_data: function() { var domains = [], contexts = [], errors = []; @@ -196,19 +275,13 @@ openerp.base.SearchView = openerp.base.Controller.extend({ } }); - if (errors.length) { - this.on_invalid(errors); - return; - } - // TODO: do we need to handle *fields* with group_by in their context? var groupbys = _(this.enabled_filters) .chain() .map(function (filter) { return filter.get_context();}) .compact() .value(); - - this.on_search(domains, contexts, groupbys); + return {domains: domains, contexts: contexts, errors: errors, groupbys: groupbys}; }, /** * Triggered after the SearchView has collected all relevant domains and @@ -293,8 +366,7 @@ openerp.base.search.fields = new openerp.base.Registry({ 'many2one': 'openerp.base.search.ManyToOneField', 'many2many': 'openerp.base.search.ManyToManyField' }); -openerp.base.search.Invalid = Class.extend( - /** @lends openerp.base.search.Invalid# */{ +openerp.base.search.Invalid = openerp.base.Class.extend( /** @lends openerp.base.search.Invalid# */{ /** * Exception thrown by search widgets when they hold invalid values, * which they can not return when asked. @@ -314,14 +386,13 @@ openerp.base.search.Invalid = Class.extend( ': [' + 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 */ @@ -412,8 +483,7 @@ openerp.base.search.Group = openerp.base.search.Widget.extend({ } }); -openerp.base.search.Input = openerp.base.search.Widget.extend( - /** @lends openerp.base.search.Input# */{ +openerp.base.search.Input = openerp.base.search.Widget.extend( /** @lends openerp.base.search.Input# */{ /** * @constructs * @extends openerp.base.search.Widget @@ -483,8 +553,7 @@ openerp.base.search.Filter = openerp.base.search.Input.extend({ return this.attrs.domain; } }); -openerp.base.search.Field = openerp.base.search.Input.extend( - /** @lends openerp.base.search.Field# */ { +openerp.base.search.Field = openerp.base.search.Input.extend( /** @lends openerp.base.search.Field# */ { template: 'SearchView.field', default_operator: '=', /** @@ -549,8 +618,7 @@ openerp.base.search.Field = openerp.base.search.Input.extend( * @class * @extends openerp.base.search.Field */ -openerp.base.search.CharField = openerp.base.search.Field.extend( - /** @lends openerp.base.search.CharField# */ { +openerp.base.search.CharField = openerp.base.search.Field.extend( /** @lends openerp.base.search.CharField# */ { default_operator: 'ilike', get_value: function () { return this.$element.val(); @@ -589,32 +657,40 @@ openerp.base.search.BooleanField = openerp.base.search.Field.extend({ } } }); -openerp.base.search.IntegerField = openerp.base.search.Field.extend({ +openerp.base.search.NumberField = openerp.base.search.Field.extend(/** @lends openerp.base.search.NumberField# */{ get_value: function () { if (!this.$element.val()) { return null; } - var val = parseInt(this.$element.val()); - var check = Number(this.$element.val()); - if (isNaN(check) || val !== check) { + var val = this.parse(this.$element.val()), + check = Number(this.$element.val()); + if (isNaN(val) || val !== check) { this.$element.addClass('error'); throw new openerp.base.search.Invalid( - this.attrs.name, this.$element.val(), "not a valid integer"); + this.attrs.name, this.$element.val(), this.error_message); } this.$element.removeClass('error'); return val; } }); -openerp.base.search.FloatField = openerp.base.search.Field.extend({ - get_value: function () { - var val = Number(this.$element.val()); - if (isNaN(val)) { - this.$element.addClass('error'); - throw new openerp.base.search.Invalid( - this.attrs.name, this.$element.val(), "not a valid number"); - } - this.$element.removeClass('error'); - return val; +/** + * @class + * @extends openerp.base.search.NumberField + */ +openerp.base.search.IntegerField = openerp.base.search.NumberField.extend(/** @lends openerp.base.search.IntegerField# */{ + error_message: "not a valid integer", + parse: function (value) { + return parseInt(value, 10); + } +}); +/** + * @class + * @extends openerp.base.search.NumberField + */ +openerp.base.search.FloatField = openerp.base.search.NumberField.extend(/** @lends openerp.base.search.FloatField# */{ + error_message: "not a valid number", + parse: function (value) { + return parseFloat(value); } }); openerp.base.search.SelectionField = openerp.base.search.Field.extend({ @@ -623,12 +699,7 @@ openerp.base.search.SelectionField = openerp.base.search.Field.extend({ return this.$element.val(); } }); -/** - * @class - * @extends openerp.base.search.Field - */ -openerp.base.search.DateField = openerp.base.search.Field.extend( - /** @lends openerp.base.search.DateField# */{ +openerp.base.search.DateField = openerp.base.search.Field.extend( /** @lends openerp.base.search.DateField# */{ template: 'SearchView.fields.date', /** * enables date picker on the HTML widgets @@ -703,29 +774,93 @@ openerp.base.search.DateField = openerp.base.search.Field.extend( openerp.base.search.DateTimeField = openerp.base.search.DateField.extend({ // TODO: time? }); -openerp.base.search.OneToManyField = openerp.base.search.IntegerField.extend({ +openerp.base.search.OneToManyField = openerp.base.search.CharField.extend({ // TODO: .relation, .context, .domain }); -openerp.base.search.ManyToOneField = openerp.base.search.IntegerField.extend({ +openerp.base.search.ManyToOneField = openerp.base.search.CharField.extend({ // TODO: @widget // TODO: .relation, .selection, .context, .domain + init: function (view_section, field, view) { + this._super(view_section, field, view); + var self = this; + this.got_name = $.Deferred().then(function () { + self.$element.val(self.name); + }); + this.dataset = new openerp.base.DataSet( + this.view, this.attrs['relation']); + }, + start: function () { + this._super(); + this.setup_autocomplete(); + var started = $.Deferred(); + this.got_name.then(function () { started.resolve();}, + function () { started.resolve(); }); + return started.promise(); + }, + setup_autocomplete: function () { + var self = this; + this.$element.autocomplete({ + source: function (req, resp) { + self.dataset.name_search( + req.term, self.attrs.domain, 'ilike', 8, function (data) { + resp(_.map(data, function (result) { + return {id: result[0], label: result[1]} + })); + }); + }, + select: function (event, ui) { + self.id = ui.item.id; + self.name = ui.item.label; + }, + delay: 0 + }) + }, + on_name_get: function (name_get) { + if (!name_get.length) { + delete this.id; + this.got_name.reject(); + return; + } + this.name = name_get[0][1]; + this.got_name.resolve(); + }, + render: function (defaults) { + if (defaults[this.attrs.name]) { + this.id = defaults[this.attrs.name]; + // TODO: maybe this should not be completely removed + delete defaults[this.attrs.name]; + this.dataset.name_get([this.id], $.proxy(this, 'on_name_get')); + } else { + this.got_name.reject(); + } + return this._super(defaults); + }, + get_domain: function () { + if (this.id && this.name) { + if (this.$element.val() === this.name) { + return [[this.attrs.name, '=', this.id]]; + } else { + delete this.id; + delete this.name; + } + } + return this._super(); + } }); -openerp.base.search.ManyToManyField = openerp.base.search.IntegerField.extend({ +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, session, model) { - this._super(parent, session); + init: function (parent, model) { + this._super(parent); this.model = model; }, 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 () { @@ -754,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() { @@ -773,12 +908,14 @@ openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({ } }, check_last_element: function() { - _.each(this.children, function(x) {x.set_last_group(false);}); - 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) { @@ -787,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(); }, @@ -804,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(); @@ -814,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(); }, @@ -828,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) { @@ -853,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(); @@ -915,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: [ @@ -932,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: [ @@ -954,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: [ @@ -976,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: [ @@ -995,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: [ @@ -1014,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: [ @@ -1028,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: [ diff --git a/addons/base/static/src/js/view_help.js b/addons/base/static/src/js/view_help.js new file mode 100644 index 00000000000..aa98f59c3ab --- /dev/null +++ b/addons/base/static/src/js/view_help.js @@ -0,0 +1,15 @@ +/*--------------------------------------------------------- + * OpenERP base library + *---------------------------------------------------------*/ + +openerp.base.view_help = function(openerp) { + +openerp.base.ProcessView = openerp.base.Widget.extend({ +}); + +openerp.base.HelpView = openerp.base.Widget.extend({ +}); + +}; + +// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/tree.js b/addons/base/static/src/js/view_tree.js similarity index 85% rename from addons/base/static/src/js/tree.js rename to addons/base/static/src/js/view_tree.js index e333c295473..90972f84973 100644 --- a/addons/base/static/src/js/tree.js +++ b/addons/base/static/src/js/view_tree.js @@ -2,10 +2,10 @@ * OpenERP base library *---------------------------------------------------------*/ -openerp.base.tree = function(openerp) { +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) */ diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index 81d55855eb8..7e95ff67f83 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -4,20 +4,32 @@ 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(session, element_id) { - this._super(session, element_id); + init: function(parent, element_id) { + this._super(parent, element_id); this.viewmanager = null; this.current_dialog = null; // Temporary linking view_manager to session. - // Will use controller_parent to find it when implementation will be done. - session.action_manager = this; + // Will use parent to find it when implementation will be done. + this.session.action_manager = this; }, /** * Process an action * Supported actions: act_window */ + action_window: function() { + }, + action_window_close: function() { + }, + action_server: function() { + }, + action_url: function() { + }, + action_report: function() { + }, + action_client: function() { + }, do_action: function(action, on_closed) { var self = this; action.flags = _.extend({ @@ -35,15 +47,12 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ action.flags.new_window = true; } if (action.target == 'new') { - var dialog = this.current_dialog = new openerp.base.ActionDialog(this.session, { - title: action.name, - width: '50%' - }); + var dialog = this.current_dialog = new openerp.base.ActionDialog(this, { title: action.name, width: '90%' }); if (on_closed) { dialog.close_callback = on_closed; } dialog.start(false); - var viewmanager = dialog.viewmanager = new openerp.base.ViewManagerAction(this.session, dialog.element_id, action); + var viewmanager = dialog.viewmanager = new openerp.base.ViewManagerAction(this, dialog.element_id, action); viewmanager.start(); dialog.open(); } else if (action.flags.new_window) { @@ -52,12 +61,15 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ var url = window.location.protocol + "//" + window.location.host + window.location.pathname + "?" + jQuery.param({ s_action : "" + key }); window.open(url); + if (on_closed) { + on_closed(); + } }); } else { if (this.viewmanager) { this.viewmanager.stop(); } - this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action); + this.viewmanager = new openerp.base.ViewManagerAction(this, this.element_id, action); this.viewmanager.start(); } break; @@ -84,9 +96,25 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ } }); -openerp.base.ViewManager = openerp.base.Controller.extend({ - init: function(session, element_id, dataset, views) { - this._super(session, element_id); +openerp.base.ActionDialog = openerp.base.Dialog.extend({ + identifier_prefix: 'action_dialog', + 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.Widget.extend({ + init: function(parent, element_id, dataset, views) { + this._super(parent, element_id); this.model = dataset.model; this.dataset = dataset; this.searchview = null; @@ -95,13 +123,13 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ {return x instanceof Array? {view_id: x[0], view_type: x[1]} : x;}); this.views = {}; this.flags = this.flags || {}; - this.sidebar = new openerp.base.NullSidebar(); this.registry = openerp.base.views; }, /** * @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})); @@ -109,7 +137,12 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ self.on_mode_switch($(this).data('view-type')); }); _.each(this.views_src, function(view) { - self.views[view.view_type] = $.extend({}, view, {controller: null}); + self.views[view.view_type] = $.extend({}, view, { + controller : null, + options : _.extend({ + sidebar_id : self.element_id + '_sidebar_' + view.view_type + }, self.flags) + }); }); if (this.flags.views_switcher === false) { this.$element.find('.oe_vm_switch').hide(); @@ -133,7 +166,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ if (!view.controller) { // Lazy loading of views var controllerclass = this.registry.get_object(view_type); - var controller = new controllerclass( this, this.session, this.element_id + "_view_" + view_type, + var controller = new controllerclass(this, this.element_id + '_view_' + view_type, this.dataset, view.view_id, view.options); if (view.embedded_view) { controller.set_embedded_view(view.embedded_view); @@ -187,7 +220,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ }, /** * Event launched when a controller has been inited. - * + * * @param {String} view_type type of view * @param {String} view the inited controller */ @@ -203,7 +236,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ if (this.searchview) { this.searchview.stop(); } - this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search", this.dataset, view_id, search_defaults); + this.searchview = new openerp.base.SearchView(this, this.element_id + "_search", this.dataset, view_id, search_defaults); if (this.flags.search_view === false) { this.searchview.hide(); } @@ -245,45 +278,39 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ }); openerp.base.NullViewManager = openerp.base.generate_null_object_class(openerp.base.ViewManager, { - init: function() { - this._super(); + init: function(parent) { + this._super(parent); + if(parent) + this.session = parent.session; this.action = {flags: {}}; - this.sidebar = new openerp.base.NullSidebar(); } }); +// TODO Will move to action Manager openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ - init: function(session, element_id, action) { + init: function(parent, element_id, action) { + this.session = parent.session; var dataset; if (!action.res_id) { - dataset = new openerp.base.DataSetSearch(session, action.res_model, action.context || null); + dataset = new openerp.base.DataSetSearch(this, action.res_model, action.context || null); } else { - dataset = new openerp.base.DataSetStatic(session, action.res_model, {}, [action.res_id]); + dataset = new openerp.base.DataSetStatic(this, action.res_model, {}, [action.res_id]); if (action.context) { // TODO fme: should normalize all DataSets constructors to (session, model, context, ...) dataset.context = action.context; } } - this._super(session, element_id, dataset, action.views); + this._super(parent, element_id, dataset, action.views); this.action = action; this.flags = this.action.flags || {}; if (action.res_model == 'board.board' && action.views.length == 1 && action.views) { // Not elegant but allows to avoid flickering of SearchView#do_hide this.flags.search_view = this.flags.pager = this.flags.sidebar = this.flags.action_buttons = false; } - if (this.flags.sidebar) { - this.sidebar = new openerp.base.Sidebar(null, this); - } }, start: function() { var inital_view_loaded = this._super(); - // init sidebar - if (this.flags.sidebar) { - this.$element.find('.view-manager-main-sidebar').html(this.sidebar.render()); - this.sidebar.start(); - } - var search_defaults = {}; _.each(this.action.context, function (value, key) { var match = /^search_default_(.*)$/.exec(key); @@ -307,8 +334,7 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ } }, stop: function() { - // should be replaced by automatic destruction implemented in BaseWidget - this.sidebar.stop(); + // should be replaced by automatic destruction implemented in Widget this._super(); }, /** @@ -335,96 +361,101 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ } }); -openerp.base.ActionDialog = openerp.base.Dialog.extend({ - identifier_prefix: 'action_dialog', - stop: function() { - this._super(this, arguments); - if (this.close_callback) { - this.close_callback(); - } - if (this.viewmanager) { - this.viewmanager.stop(); - } - } -}); - -openerp.base.Sidebar = openerp.base.BaseWidget.extend({ - template: "ViewManager.sidebar", - init: function(parent, view_manager) { - this._super(parent, view_manager.session); - this.view_manager = view_manager; - this.sections = []; - }, - set_toolbar: function(toolbar) { - this.sections = []; - var self = this; - _.each([["print", "Reports"], ["action", "Actions"], ["relate", "Links"]], function(type) { - if (toolbar[type[0]].length == 0) - return; - var section = {elements:toolbar[type[0]], label:type[1]}; - self.sections.push(section); - }); - this.do_refresh(true); - }, - do_refresh: function(new_view) { - var view = this.view_manager.active_view; - var the_condition = this.sections.length > 0 && _.detect(this.sections, - function(x) {return x.elements.length > 0;}) != undefined - && (!new_view || view != 'list'); - - this.$element.toggleClass('open-sidebar', the_condition) - .toggleClass('closed-sidebar', !the_condition); - - this.$element.html(QWeb.render("ViewManager.sidebar.internal", { sidebar: this, view: view })); - - var self = this; - this.$element.find(".toggle-sidebar").click(function(e) { - self.$element.toggleClass('open-sidebar closed-sidebar'); - e.stopPropagation(); - e.preventDefault(); - }); - - this.$element.find("a.oe_sidebar_action_a").click(function(e) { - var $this = jQuery(this); - var index = $this.attr("data-index").split('-'); - var action = self.sections[index[0]].elements[index[1]]; - action.flags = { - new_window : true - }; - self.session.action_manager.do_action(action); - e.stopPropagation(); - e.preventDefault(); - }); +openerp.base.Sidebar = openerp.base.Widget.extend({ + init: function(parent, element_id) { + this._super(parent, element_id); + this.items = {}; + this.sections = {}; }, start: function() { - this._super(); - this.do_refresh(false); + var self = this; + this._super(this, arguments); + this.$element.html(QWeb.render('Sidebar')); + this.$element.find(".toggle-sidebar").click(function(e) { + self.do_toggle(); + }); + }, + add_toolbar: function(toolbar) { + var self = this; + _.each([['print', "Reports"], ['action', "Actions"], ['relate', "Links"]], function(type) { + var items = toolbar[type[0]]; + if (items.length) { + for (var i = 0; i < items.length; i++) { + items[i] = { + label: items[i]['name'], + action: items[i], + classname: 'oe_sidebar_' + type[0] + } + } + self.add_section(type[0], type[1], 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. + // Else, the section is a standard section with items displayed as links. + // An item is a dictonary : { + // 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: 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_' + code + '_'); + if (items) { + for (var i = 0; i < items.length; i++) { + items[i].element_id = _.uniqueId(section_id + '_item_'); + this.items[items[i].element_id] = items[i]; + } + } + var $section = $(QWeb.render("Sidebar.section", { + section_id: section_id, + name: name, + classname: 'oe_sidebar_' + code, + items: items + })); + if (items) { + $section.find('a.oe_sidebar_action_a').click(function() { + var item = self.items[$(this).attr('id')]; + if (item.callback) { + item.callback(); + } + if (item.action) { + item.action.flags = item.action.flags || {}; + item.action.flags.new_window = true; + self.do_action(item.action); + } + return false; + }); + } + $section.appendTo(this.$element.find('div.sidebar-actions')); + this.sections[code] = $section; + return section_id; + }, + do_fold: function() { + this.$element.addClass('closed-sidebar').removeClass('open-sidebar'); + }, + do_unfold: function() { + this.$element.addClass('open-sidebar').removeClass('closed-sidebar'); + }, + do_toggle: function() { + this.$element.toggleClass('open-sidebar closed-sidebar'); } }); - -openerp.base.NullSidebar = openerp.base.generate_null_object_class(openerp.base.Sidebar); - -openerp.base.Export = openerp.base.Dialog.extend({ - dialog_title: "Export", - template: 'ExportDialog', - identifier_prefix: 'export_dialog', - init: function (session, model, domain) { - this._super(); +openerp.base.View = openerp.base.Widget.extend({ + set_default_options: function(options) { + this.options = options || {}; + _.defaults(this.options, { + // All possible views options should be defaulted here + sidebar_id: null, + sidebar: true + }); }, - 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({ /** * Fetches and executes the action identified by ``action_data``. * @@ -480,11 +511,65 @@ openerp.base.View = openerp.base.Controller.extend({ * Directly set a view to use instead of calling fields_view_get. This method must * be called before start(). When an embedded view is set, underlying implementations * of openerp.base.View must use the provided view instead of any other one. - * + * * @param embedded_view A view. */ 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() { } }); @@ -493,12 +578,6 @@ openerp.base.View = openerp.base.Controller.extend({ */ openerp.base.views = new openerp.base.Registry(); -openerp.base.ProcessView = openerp.base.Controller.extend({ -}); - -openerp.base.HelpView = openerp.base.Controller.extend({ -}); - openerp.base.json_node_to_xml = function(node, single_quote, indent) { // For debugging purpose, this function will convert a json node back to xml // Maybe usefull for xml view editor diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index 8f9d063ffba..c51284ad63a 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -62,7 +62,7 @@ - + - - + + + t-att-onclick="view.datarecord.id ? null : 'alert(\'No record selected ! You can only attach to existing record.\'); return false;'"/> @@ -385,7 +405,7 @@
  • + + '&field=datas&fieldname=name&t=' + (new Date().getTime())"/> @@ -522,7 +542,7 @@ @@ -665,14 +685,28 @@ + + + + + + + + + + + + +
    +

    Filter Name:

    + +

    (Any existing filter with the same name will be replaced)

    +
    +
    @@ -787,7 +821,7 @@ -
    @@ -836,29 +870,6 @@ - - - - - - -
    @@ -933,4 +944,145 @@ .unwrap(); + + + Export + + + +
    + + + + + + + + + + +
    + 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. +
    + + + + + + + + +
    + + + +
    +
    + + + + + + + + + + + +
    Available fieldsFields to export + Save fields list +
    +
    +
    +
    +
    +
    + + + + + + + + + + +
    + +
    + +
    + +
    +
    + +
    +
    +
    + + + + + +
    Name
    +
    + + + + + + + + + + + + + + +
    &nbsp; + + + + + + + + + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + diff --git a/addons/base/static/test/list.js b/addons/base/static/test/list.js index 3d441f190db..ebce7e8535a 100644 --- a/addons/base/static/test/list.js +++ b/addons/base/static/test/list.js @@ -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}); diff --git a/addons/base_calendar/static/src/js/calendar.js b/addons/base_calendar/static/src/js/calendar.js index 96a7fad8178..82f59e7c0cf 100644 --- a/addons/base_calendar/static/src/js/calendar.js +++ b/addons/base_calendar/static/src/js/calendar.js @@ -5,11 +5,11 @@ openerp.base_calendar = function(openerp) { 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.Controller.extend({ +openerp.base_calendar.CalendarView = openerp.base.View.extend({ // Dhtmlx scheduler ? - init: function(view_manager, session, element_id, dataset, view_id){ - this._super(session, element_id); - this.view_manager = view_manager; + init: function(parent, element_id, dataset, view_id) { + this._super(parent, element_id); + this.view_manager = parent || new openerp.base.NullViewManager(); this.dataset = dataset; this.dataset_index = 0; this.model = dataset.model; @@ -194,7 +194,9 @@ openerp.base_calendar.CalendarView = openerp.base.Controller.extend({ // self.color_field console.log('Reload Scheduler>>>') }, - + do_search: function (domains, contexts, groupbys) { + this.notification.notify("Searching caldendar"); + }, do_show: function () { this.$element.show(); }, diff --git a/addons/base_dashboard/static/src/js/dashboard.js b/addons/base_dashboard/static/src/js/dashboard.js index 3a70aa649ea..6b65b588d35 100644 --- a/addons/base_dashboard/static/src/js/dashboard.js +++ b/addons/base_dashboard/static/src/js/dashboard.js @@ -89,7 +89,7 @@ openerp.base.form.DashBoard = openerp.base.form.Widget.extend({ } } }); - new openerp.base.ViewManagerAction(this.session, dialog_id, action).start(); + new openerp.base.ViewManagerAction(this, dialog_id, action).start(); // TODO: should bind ListView#select_record in order to catch record clicking }, do_add_widget : function(action_manager) { @@ -227,7 +227,7 @@ openerp.base.form.DashBoard = openerp.base.form.Widget.extend({ action_buttons : false, pager: false }; - new openerp.base.ViewManagerAction(this.session, + new openerp.base.ViewManagerAction(this, this.view.element_id + '_action_' + action.id, action).start(); }, render: function() { diff --git a/addons/base_diagram/static/src/js/diagram.js b/addons/base_diagram/static/src/js/diagram.js index af56c256ee7..b992b5cbf42 100644 --- a/addons/base_diagram/static/src/js/diagram.js +++ b/addons/base_diagram/static/src/js/diagram.js @@ -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; diff --git a/addons/base_gantt/static/src/js/gantt.js b/addons/base_gantt/static/src/js/gantt.js index e3c2b79fc58..06ec4be2634 100644 --- a/addons/base_gantt/static/src/js/gantt.js +++ b/addons/base_gantt/static/src/js/gantt.js @@ -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) { diff --git a/addons/base_graph/static/src/js/graph.js b/addons/base_graph/static/src/js/graph.js index fdf3585449c..25ce024638b 100644 --- a/addons/base_graph/static/src/js/graph.js +++ b/addons/base_graph/static/src/js/graph.js @@ -16,9 +16,9 @@ QWeb.add_template('/base_graph/static/src/xml/base_graph.xml'); openerp.base.views.add('graph', 'openerp.base_graph.GraphView'); openerp.base_graph.GraphView = openerp.base.View.extend({ - init: function(view_manager, session, element_id, dataset, view_id) { - this._super(session, element_id); - this.view_manager = view_manager; + init: function(parent, element_id, dataset, view_id) { + this._super(parent, element_id); + this.view_manager = parent; this.dataset = dataset; this.dataset_index = 0; this.model = this.dataset.model; @@ -76,12 +76,20 @@ openerp.base_graph.GraphView = openerp.base.View.extend({ load_chart: function(data) { var self = this; + var domain = false; if(data){ this.x_title = this.all_fields[this.chart_info_fields]['string']; this.y_title = this.all_fields[this.operator_field]['string']; self.schedule_chart(data); }else{ - this.dataset.read_slice(this.fields, 0, false, function(res) { + if(! _.isEmpty(this.view_manager.dataset.domain)){ + domain = this.view_manager.dataset.domain; + }else if(! _.isEmpty(this.view_manager.action.domain)){ + domain = this.view_manager.action.domain; + } + this.dataset.domain = domain; + this.dataset.context = this.view_manager.dataset.context; + this.dataset.read_slice(_(this.fields).keys(), 0, false, function(res) { self.schedule_chart(res); }); } diff --git a/addons/base_graph/static/src/xml/base_graph.xml b/addons/base_graph/static/src/xml/base_graph.xml index cb5b1b2b0c4..4320b9f156e 100644 --- a/addons/base_graph/static/src/xml/base_graph.xml +++ b/addons/base_graph/static/src/xml/base_graph.xml @@ -1,6 +1,4 @@ +
    + \ No newline at end of file diff --git a/addons/web_mobile/static/src/css/web_mobile.css b/addons/web_mobile/static/src/css/web_mobile.css index e69de29bb2d..18cf93f4d66 100644 --- a/addons/web_mobile/static/src/css/web_mobile.css +++ b/addons/web_mobile/static/src/css/web_mobile.css @@ -0,0 +1,4 @@ + +.ui-collapsible-content-expanded { + display: block; +} diff --git a/addons/web_mobile/static/src/js/chrome_mobile.js b/addons/web_mobile/static/src/js/chrome_mobile.js new file mode 100644 index 00000000000..785cf98b6aa --- /dev/null +++ b/addons/web_mobile/static/src/js/chrome_mobile.js @@ -0,0 +1,217 @@ +openerp.web_mobile.chrome_mobile = function(openerp) { +openerp.web_mobile.Shortcuts = openerp.base.Widget.extend({ + init: function(session, element_id) { + this._super(session, element_id); + }, + start: function() { + var self = this; + this.rpc('/base/session/sc_list',{} ,function(res){ + self.$element.html(QWeb.render("Shortcuts", {'sc' : res})) + self.$element.find("a").click(self.on_clicked); + }) + }, + on_clicked: function(ev) { + $shortcut = $(ev.currentTarget); + id = $shortcut.data('menu'); + res_id = $shortcut.data('res'); + jQuery("#oe_header").find("h1").html($shortcut.data('name')); + this.listview = new openerp.web_mobile.ListView(this.session, "oe_app", res_id); + this.listview.start(); + } +}); +openerp.web_mobile.Header = openerp.base.Widget.extend({ + init: function(session, element_id) { + this._super(session, element_id); + }, + start: function() { + this.$element.html(QWeb.render("Header", this)); + this.$element.find("a").click(this.on_clicked); + }, + on_clicked: function(ev) { + $opt = $(ev.currentTarget); + current_id = $opt.attr('id'); + if (current_id == 'home') { + this.homepage = new openerp.web_mobile.Login(this.session, "oe_app"); + this.homepage.on_login_valid(); + } + } +}); +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; + }, + start: function(ev, id) { + var v = { menu : this.data }; + this.$element.html(QWeb.render("Menu.secondary", v)); + this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click); + }, + on_menu_click: function(ev, id) { + $menu = $(ev.currentTarget); + id = $menu.data('menu'); + for (var i = 0; i < this.data.children.length; i++) { + if (this.data.children[i].id == id) { + this.children = this.data.children[i]; + } + } + jQuery("#oe_header").find("h1").html($menu.data('name')); + + var child_len = this.children.children.length; + if (child_len > 0) { + this.$element + .removeClass("secondary_menu") + .addClass("content_menu"); + //.hide(); + this.secondary = new openerp.web_mobile.Secondary(this.session, "oe_app", this.children); + this.secondary.start(); + } + else { + if (id) { + this.listview = new openerp.web_mobile.ListView(this.session, "oe_app", id); + this.listview.start(); + } + } + } +}); + +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; + this.$secondary_menu = $("#" + secondary_menu_id); + this.menu = false; + }, + start: function() { + this.rpc("/base/menu/load", {}, this.on_loaded); + }, + on_loaded: function(data) { + this.data = data; + this.$element.html(QWeb.render("Menu", this.data)); + this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click); + }, + on_menu_click: function(ev, id) { + $menu = $(ev.currentTarget); + id = $menu.data('menu'); + for (var i = 0; i < this.data.data.children.length; i++) { + if (this.data.data.children[i].id == id) { + this.children = this.data.data.children[i]; + } + } + jQuery("#oe_header").find("h1").html($menu.data('name')); + this.$element + .removeClass("login_valid") + .addClass("secondary_menu"); + //.hide(); + this.secondary = new openerp.web_mobile.Secondary(this.session, "oe_app", this.children); + this.secondary.start(); + } +}); +openerp.web_mobile.Options = openerp.base.Widget.extend({ + init: function(session, element_id) { + this._super(session, element_id); + }, + start: function() { + var self = this; + this.$element.html(QWeb.render("Options", this)); + self.$element.find("#logout").click(self.on_logout); + }, + on_logout: function(ev) { + this.session.logout(); + this.login = new openerp.web_mobile.Login(this.session, "oe_app"); + this.login.start(); + } +}); +openerp.web_mobile.Login = openerp.base.Widget.extend({ + init: function(session, element_id) { + this._super(session, element_id); + }, + start: function() { + var self = this; + + jQuery("#oe_header").children().remove(); + this.rpc("/base/session/get_databases_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); + self.$element.find("#login").click(self.on_login); + $.mobile.initializePage(); + }) + }, + on_db_select: function(ev) { + var db = this.$element.find("#database option:selected").val(); + jQuery("#db_text").html(db); + }, + on_login: function(ev) { + ev.preventDefault(); + var self = this; + var $e = this.$element; + var db = $e.find("div select[name=database]").val(); + var login = $e.find("div input[name=login]").val(); + var password = $e.find("div input[name=password]").val(); + //$e.hide(); + // Should hide then call callback + this.session.session_login(db, login, password, function() { + if(self.session.session_is_valid()) { + self.on_login_valid(); + } else { + self.on_login_invalid(); + } + }); + }, + on_login_invalid: function() { + this.$element + .removeClass("login_valid") + .addClass("login_invalid") + .show(); + }, + on_login_valid: function() { + this.$element + .removeClass("login_invalid") + .addClass("login_valid"); + //.hide(); + this.$element.html(QWeb.render("HomePage", {})); + this.header = new openerp.web_mobile.Header(this.session, "oe_header"); + this.shortcuts = new openerp.web_mobile.Shortcuts(this.session, "oe_shortcuts"); + this.menu = new openerp.web_mobile.Menu(this.session, "oe_menu", "oe_secondary_menu"); + this.options = new openerp.web_mobile.Options(this.session, "oe_options"); + this.header.start(); + this.shortcuts.start(); + this.menu.start(); + this.options.start(); + jQuery("#oe_header").find("h1").html('Home'); + + }, + do_ask_login: function(continuation) { + this.on_login_invalid(); + this.on_login_valid.add({ + position: "last", + unique: true, + callback: continuation + }); + } +}); +openerp.web_mobile.MobileWebClient = openerp.base.Widget.extend({ + init: function(element_id) { + var self = this; + this._super(null, element_id); + QWeb.add_template("xml/web_mobile.xml"); + var params = {}; + + this.$element.html(QWeb.render("WebClient", {})); + this.session = new openerp.base.Session("oe_errors"); + this.crashmanager = new openerp.base.CrashManager(this.session); + this.login = new openerp.web_mobile.Login(this.session, "oe_app"); + this.session.on_session_invalid.add(this.login.do_ask_login); + }, + start: function() { + this.session.start(); + this.login.start(); + } +}); +openerp.web_mobile.mobilewebclient = function(element_id) { + // TODO Helper to start mobile webclient rename it openerp.base.webclient + var client = new openerp.web_mobile.MobileWebClient(element_id); + client.start(); + return client; +}; +} \ No newline at end of file diff --git a/addons/web_mobile/static/src/js/form_mobile.js b/addons/web_mobile/static/src/js/form_mobile.js new file mode 100644 index 00000000000..51bfd719c7f --- /dev/null +++ b/addons/web_mobile/static/src/js/form_mobile.js @@ -0,0 +1,104 @@ +openerp.web_mobile.form_mobile = function (openerp) { +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; + this.action = action; + }, + start: function() { + var self = this; + id = this.list_id; + model = this.action.res_model; + view_id = this.action.views[1][0]; + + this.dataset = new openerp.base.DataSetSearch(this.session, this.action.res_model, null, null); + this.dataset.read_slice(false, false, false, function (result) { + + for (var i = 0; i < result.length; i++) { + if (result[i].id == id) { + var data = result[i]; + } + } + self.rpc("/base/formview/load", {"model": model, "view_id": view_id }, function (result) { + var view_fields = result.fields_view.arch.children; + var get_fields = self.get_fields(view_fields); + + for (var j = 0; j < view_fields.length; j++) { + if (view_fields[j].tag == 'notebook') { + var notebooks = view_fields[j]; + } + } + $("#oe_header").find("h1").html(result.fields_view.arch.attrs.string); + self.$element.html(QWeb.render("FormView", {'get_fields': get_fields, 'notebooks': notebooks || false, 'fields' : result.fields_view.fields, 'values' : data })); + + self.$element.find('a').click(function(){ + + for (var k = 0; k < notebooks.children.length; k++) { + + var text = $(this).find('.ui-btn-text').text(); + var next = $(this).next(); + var span = $(this).find('span .ui-icon'); + var content_expanded_class = "ui-collapsible-content ui-collapsible-content-expanded"; + var content_collapsed_class = "ui-collapsible-content ui-collapsible-content-collapsed"; + var expanded_class = "ui-collapsible-content-expanded"; + var collapsed_class = "ui-collapsible-content-collapsed"; + var plus_class = "ui-icon-plus"; + var minus_class = "ui-icon-minus"; + + if (notebooks.children[k].attrs.string == $.trim(text)) { + get_fields = self.get_fields(notebooks.children[k].children); + + $(this).parents().find('form').find('div').each( function() { + var prev = $(this).prev(); + var trim_class = $.trim($(this).attr('class')); + if (trim_class == content_expanded_class || trim_class == content_collapsed_class) { + prev.removeClass('ui-btn-active'); + + if ($.trim(prev.find('.ui-btn-text').text()) != notebooks.children[k].attrs.string) { + prev.find('span .ui-icon').removeClass(minus_class); + prev.find('span .ui-icon').addClass(plus_class); + $(this).removeClass(expanded_class); + $(this).addClass(collapsed_class); + } + } + }); + + if (next.hasClass(content_collapsed_class)) { + span.removeClass(plus_class); + span.addClass(minus_class); + next.removeClass(collapsed_class); + next.addClass(expanded_class); + } + else if (next.hasClass(content_expanded_class)) { + span.removeClass(minus_class); + span.addClass(plus_class); + next.removeClass(expanded_class); + next.addClass(collapsed_class); + } + if (!next.find('.detail').html().length) { + next.find('.detail').append(QWeb.render("FormView", {'get_fields': get_fields,'fields' : result.fields_view.fields, 'values' : data })); + } + } + } + }); + }); + }); + }, + get_fields: function(view_fields, fields) { + this.fields = fields || []; + for (var i=0; i < view_fields.length; i++){ + if (view_fields[i].tag == 'field') { + this.fields.push(view_fields[i]); + } + if (view_fields[i].tag == 'group') { + this.get_fields(view_fields[i].children, this.fields); + } + if (view_fields[i].tag == 'separator') { + this.get_fields(view_fields[i].children, this.fields); + } + } + return this.fields; + }, +}); + +} \ No newline at end of file diff --git a/addons/web_mobile/static/src/js/list_mobile.js b/addons/web_mobile/static/src/js/list_mobile.js new file mode 100644 index 00000000000..809797bfe0f --- /dev/null +++ b/addons/web_mobile/static/src/js/list_mobile.js @@ -0,0 +1,67 @@ +openerp.web_mobile.list_mobile = function (openerp) { +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; + }, + start: function() { + this.rpc('/base/menu/action', {'menu_id': this.list_id}, + this.on_menu_action_loaded); + }, + on_menu_action_loaded: function(data) { + var self = this; + if (data.action.length) { + this.action = data.action[0][2]; + this.on_search_data(''); + } + }, + on_search_data: function(request){ + if(request){ + if(request.term){ + var search_val = request.term; + }else{ + if(request.which==27 || request.which==13 || request.which==9){ + var search_val = ''; + }else if(request.which==38 || request.which==40 || request.which==39 || request.which==37){ + return; + }else if($("#searchid").val()==""){ + var search_val = ''; + }else{ + return; + } + } + } + else{ + var search_val = ''; + } + var self = this; + var dataset = new openerp.base.DataSetStatic(this.session, this.action.res_model, this.action.context); + dataset.domain=[['name','ilike',search_val]]; + dataset.name_search(search_val, dataset.domain, 'ilike',false ,function(result){ + self.$element.html(QWeb.render("ListView", {'records' : result.result})); + self.$element.find("#searchid").focus(); + if(request.term){ + self.$element.find("#searchid").val(request.term); + } + self.$element.find("#searchid").autocomplete({ + source: function(req) { self.on_search_data(req); }, + focus: function(e, ui) { + e.preventDefault(); + }, + html: true, + minLength: 0, + delay: 0 + }); + self.$element.find("#searchid").keyup(self.on_search_data); + self.$element.find("a#list-id").click(self.on_list_click); + }); + }, + on_list_click: function(ev) { + $record = $(ev.currentTarget); + var self = this; + id = $record.data('id'); + this.formview = new openerp.web_mobile.FormView(this.session, "oe_app", id, this.action); + this.formview.start(); + } + }); +} \ No newline at end of file diff --git a/addons/web_mobile/static/src/js/web_mobile.js b/addons/web_mobile/static/src/js/web_mobile.js index 059c374d7d4..cdf25b22b38 100644 --- a/addons/web_mobile/static/src/js/web_mobile.js +++ b/addons/web_mobile/static/src/js/web_mobile.js @@ -1,257 +1,5 @@ - -openerp.web_mobile = function(openerp) { - -openerp.web_mobile = {}; - -openerp.web_mobile.MobileWebClient = openerp.base.Controller.extend({ - init: function(element_id) { - var self = this; - this._super(null, element_id); - QWeb.add_template("xml/web_mobile.xml"); - var params = {}; - - this.$element.html(QWeb.render("WebClient", {})); - this.session = new openerp.base.Session("oe_errors"); - this.crashmanager = new openerp.base.CrashManager(this.session); - this.login = new openerp.web_mobile.Login(this.session, "oe_app"); - - this.session.on_session_invalid.add(this.login.do_ask_login); - - }, - start: function() { - this.session.start(); - this.login.start(); - } -}); - -openerp.web_mobile.mobilewebclient = function(element_id) { - // TODO Helper to start mobile webclient rename it openerp.base.webclient - var client = new openerp.web_mobile.MobileWebClient(element_id); - client.start(); - return client; -}; - -openerp.web_mobile.Header = openerp.base.Controller.extend({ - init: function(session, element_id) { - this._super(session, element_id); - }, - start: function() { - this.$element.html(QWeb.render("Header", this)); - this.$element.find("a").click(this.on_clicked); - }, - on_clicked: function(ev) { - $opt = $(ev.currentTarget); - current_id = $opt.attr('id'); - if (current_id == 'home') { - this.homepage = new openerp.web_mobile.Login(this.session, "oe_app"); - this.homepage.on_login_valid(); - } - } - -}); - -openerp.web_mobile.Shortcuts = openerp.base.Controller.extend({ - init: function(session, element_id) { - this._super(session, element_id); - }, - start: function() { - var self = this; - this.rpc('/base/session/sc_list',{} ,function(res){ - self.$element.html(QWeb.render("Shortcuts", {'sc' : res})) - self.$element.find("a").click(self.on_clicked); - }) - }, - on_clicked: function(ev) { - $shortcut = $(ev.currentTarget); - id = $shortcut.data('menu'); - res_id = $shortcut.data('res'); - jQuery("#oe_header").find("h1").html($shortcut.data('name')); - this.listview = new openerp.web_mobile.ListView(this.session, "oe_app", res_id); - this.listview.start(); - } -}); - -openerp.web_mobile.ListView = openerp.base.Controller.extend({ - init: function(session, element_id, list_id) { - this._super(session, element_id); - this.list_id = list_id; - }, - start: function() { - this.rpc('/base/menu/action', {'menu_id': this.list_id}, - this.on_menu_action_loaded); - }, - on_menu_action_loaded: function(data) { - var self = this; - if (data.action.length) { - var action = data.action[0][2]; - self.on_action(action); - } - }, - on_action: function(action) { - var self = this; - var view_id = action.views[0][0]; - (new openerp.base.DataSetSearch(this.session, action.res_model, null, null)) - .read_slice(false, false, false, function(result){ - this.listview = new openerp.web_mobile.ListView(this.session, "oe_app"); - self.$element.html(QWeb.render("ListView", {'records' : result})); - }); - } - }); - -openerp.web_mobile.Secondary = openerp.base.Controller.extend({ - init: function(session, element_id, secondary_menu_id) { - this._super(session, element_id); - this.data = secondary_menu_id; - }, - start: function(ev, id) { - var v = { menu : this.data }; - this.$element.html(QWeb.render("Menu.secondary", v)); - this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click); - }, - on_menu_click: function(ev, id) { - $menu = $(ev.currentTarget); - id = $menu.data('menu'); - for (var i = 0; i < this.data.children.length; i++) { - if (this.data.children[i].id == id) { - this.children = this.data.children[i]; - } - } - jQuery("#oe_header").find("h1").html($menu.data('name')); - - var child_len = this.children.children.length; - if (child_len > 0) { - this.$element - .removeClass("secondary_menu") - .addClass("content_menu"); - //.hide(); - this.secondary = new openerp.web_mobile.Secondary(this.session, "oe_app", this.children); - this.secondary.start(); - } - else { - if (id) { - this.listview = new openerp.web_mobile.ListView(this.session, "oe_app", id); - this.listview.start(); - } - } - } -}); - -openerp.web_mobile.Menu = openerp.base.Controller.extend({ - init: function(session, element_id, secondary_menu_id) { - this._super(session, element_id); - this.secondary_menu_id = secondary_menu_id; - this.$secondary_menu = $("#" + secondary_menu_id); - this.menu = false; - }, - start: function() { - this.rpc("/base/menu/load", {}, this.on_loaded); - }, - on_loaded: function(data) { - this.data = data; - this.$element.html(QWeb.render("Menu", this.data)); - this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click); - }, - on_menu_click: function(ev, id) { - $menu = $(ev.currentTarget); - id = $menu.data('menu'); - for (var i = 0; i < this.data.data.children.length; i++) { - if (this.data.data.children[i].id == id) { - this.children = this.data.data.children[i]; - } - } - jQuery("#oe_header").find("h1").html($menu.data('name')); - this.$element - .removeClass("login_valid") - .addClass("secondary_menu"); - //.hide(); - this.secondary = new openerp.web_mobile.Secondary(this.session, "oe_app", this.children); - this.secondary.start(); - } -}); - -openerp.web_mobile.Options = openerp.base.Controller.extend({ - init: function(session, element_id) { - this._super(session, element_id); - }, - start: function() { - var self = this; - this.$element.html(QWeb.render("Options", this)); - self.$element.find("#logout").click(self.on_logout); - }, - on_logout: function(ev) { - this.session.logout(); - this.login = new openerp.web_mobile.Login(this.session, "oe_app"); - this.login.start(); - } -}); -openerp.web_mobile.Login = openerp.base.Controller.extend({ - init: function(session, element_id) { - this._super(session, element_id); - }, - start: function() { - var self = this; - - jQuery("#oe_header").children().remove(); - this.rpc("/base/session/get_databases_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); - self.$element.find("#login").click(self.on_login); - $.mobile.initializePage(); - }) - }, - on_db_select: function(ev) { - var db = this.$element.find("#database option:selected").val(); - jQuery("#db_text").html(db); - }, - on_login: function(ev) { - ev.preventDefault(); - var self = this; - var $e = this.$element; - var db = $e.find("div select[name=database]").val(); - var login = $e.find("div input[name=login]").val(); - var password = $e.find("div input[name=password]").val(); - //$e.hide(); - // Should hide then call callback - this.session.session_login(db, login, password, function() { - if(self.session.session_is_valid()) { - self.on_login_valid(); - } else { - self.on_login_invalid(); - } - }); - }, - on_login_invalid: function() { - this.$element - .removeClass("login_valid") - .addClass("login_invalid") - .show(); - }, - on_login_valid: function() { - this.$element - .removeClass("login_invalid") - .addClass("login_valid"); - //.hide(); - this.$element.html(QWeb.render("HomePage", {})); - this.header = new openerp.web_mobile.Header(this.session, "oe_header"); - this.shortcuts = new openerp.web_mobile.Shortcuts(this.session, "oe_shortcuts"); - this.menu = new openerp.web_mobile.Menu(this.session, "oe_menu", "oe_secondary_menu"); - this.options = new openerp.web_mobile.Options(this.session, "oe_options"); - this.header.start(); - this.shortcuts.start(); - this.menu.start(); - this.options.start(); - jQuery("#oe_header").find("h1").html('Home'); - - }, - do_ask_login: function(continuation) { - this.on_login_invalid(); - this.on_login_valid.add({ - position: "last", - unique: true, - callback: continuation - }); - } -}); - +openerp.web_mobile = function(instance) { + openerp.web_mobile.chrome_mobile(instance); + openerp.web_mobile.list_mobile(instance); + openerp.web_mobile.form_mobile(instance); }; diff --git a/addons/web_mobile/static/src/web_mobile.html b/addons/web_mobile/static/src/web_mobile.html index bc95e40eb43..e3ff683dd28 100755 --- a/addons/web_mobile/static/src/web_mobile.html +++ b/addons/web_mobile/static/src/web_mobile.html @@ -2,11 +2,13 @@ - Login Page OpenERP mobile + OpenERP Web Mobile - + + + @@ -17,21 +19,23 @@ - + - + + + + + - +
    diff --git a/addons/web_mobile/static/src/xml/web_mobile.xml b/addons/web_mobile/static/src/xml/web_mobile.xml index 2f8a464ab72..fa5fe0c6b27 100644 --- a/addons/web_mobile/static/src/xml/web_mobile.xml +++ b/addons/web_mobile/static/src/xml/web_mobile.xml @@ -36,7 +36,7 @@ Choose Database... - @@ -67,12 +67,17 @@
    -
      + +
      • @@ -82,6 +87,151 @@
        + +
        +
        + +
        + + + + +
        +
        + +
        +
        +
        + + +
        +
        + +
        +
        +
        + +
        +
        + +
        +
        + + + + + + + + + + + + + +
        +
        +
        + +
        +
        + + + + + + + +
        +
        +
        + + +
        +
        + +
        +
        +
        + + +
        +
        + +
        +
        +
        + + +
        +
        + +
        +
        +
        + + +
        +
        + +
        +
        +
        + + +
        +
        + +
        +
        +
        +
        +
        + + + + + +
        +
        +
        + +
        • diff --git a/openerpweb/openerpweb.py b/openerpweb/openerpweb.py index eee0a4eec20..1e5c3cf8b92 100644 --- a/openerpweb/openerpweb.py +++ b/openerpweb/openerpweb.py @@ -407,7 +407,7 @@ class Root(object): if path_addons not in sys.path: sys.path.insert(0, path_addons) for i in os.listdir(path_addons): - if i not in sys.modules: + if i not in addons_module: manifest_path = os.path.join(path_addons, i, '__openerp__.py') if os.path.isfile(manifest_path): manifest = eval(open(manifest_path).read()) @@ -445,7 +445,7 @@ class Root(object): #for the mobile web client we are supposed to use a different url to just add '/mobile' raise cherrypy.HTTPRedirect('/web_mobile/static/src/web_mobile.html', 301) else: - raise cherrypy.HTTPRedirect('/base/static/src/base.html', 301) + raise cherrypy.HTTPRedirect('/base/webclient/home', 301) default.exposed = True def main(argv):