diff options
| author | noz (OpenERP) <noz@tinyerp.com> | 2011-07-27 11:08:22 +0530 |
|---|---|---|
| committer | noz (OpenERP) <noz@tinyerp.com> | 2011-07-27 11:08:22 +0530 |
| commit | fca67e2ab402f4d6c85d4afbea135a4150585dd6 (patch) | |
| tree | 36955c5d3493b9cb2102a140139648c5d0dabaad /addons/base | |
| parent | c918d3d9fc17220a33d3284a7b3f391269cdb3cf (diff) | |
| parent | d0234fa5dd424e7722443fda7e41f955d857027a (diff) | |
[Merge] Merged with main branch.
bzr revid: noz@tinyerp.com-20110727053822-olbfv0ljww17cz9c
Diffstat (limited to 'addons/base')
| -rw-r--r-- | addons/base/__openerp__.py | 33 | ||||
| -rw-r--r-- | addons/base/controllers/main.py | 183 | ||||
| -rw-r--r-- | addons/base/static/lib/LABjs/LAB.js | 5 | ||||
| -rw-r--r-- | addons/base/static/src/base.html | 56 | ||||
| -rw-r--r-- | addons/base/static/src/css/base.css | 4 | ||||
| -rw-r--r-- | addons/base/static/src/js/chrome.js | 310 | ||||
| -rw-r--r-- | addons/base/static/src/js/controller.js | 56 | ||||
| -rw-r--r-- | addons/base/static/src/js/data.js | 7 | ||||
| -rw-r--r-- | addons/base/static/src/js/form.js | 117 | ||||
| -rw-r--r-- | addons/base/static/src/js/list.js | 166 | ||||
| -rw-r--r-- | addons/base/static/src/js/search.js | 148 | ||||
| -rw-r--r-- | addons/base/static/src/js/views.js | 173 | ||||
| -rw-r--r-- | addons/base/static/src/xml/base.xml | 86 |
13 files changed, 820 insertions, 524 deletions
diff --git a/addons/base/__openerp__.py b/addons/base/__openerp__.py index da2b345d217..e17e2d82c6d 100644 --- a/addons/base/__openerp__.py +++ b/addons/base/__openerp__.py @@ -3,4 +3,37 @@ "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.validate/jquery.validate.js", + "static/lib/jquery.ba-bbq/jquery.ba-bbq.js", + "static/lib/jquery.contextmenu/jquery.contextmenu.r2.packed.js", + "static/lib/jquery.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/base.js", + "static/src/js/chrome.js", + "static/src/js/controller.js", + "static/src/js/views.js", + "static/src/js/data.js", + "static/src/js/dates.js", + "static/src/js/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", + ], } diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 2507f958cdc..8fe79a19a9a 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -56,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 ="""<!DOCTYPE html> + <html style="height: 100%%"> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <title>OpenERP</title> + %s + <script type="text/javascript"> + $(function() { + QWeb = new QWeb2.Engine(); + openerp.init().base.webclient("oe"); + }); + </script> + <link rel="shortcut icon" href="/base/static/src/img/favicon.ico" type="image/x-icon"/> + %s + <!--[if lte IE 7]> + <link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/> + <![endif]--> + </head> + <body id="oe" class="openerp"></body> + </html> + """.replace('\n'+' '*8,'\n') + + # script tags + jslist = ['/base/webclient/js'] + if 1: # debug == 1 + jslist = manifest_glob(['base'], 'js') + js = "\n ".join(['<script type="text/javascript" src="%s"></script>'%i for i in jslist]) + + # css tags + csslist = ['/base/webclient/css'] + if 1: # debug == 1 + csslist = manifest_glob(['base'], 'css') + css = "\n ".join(['<link rel="stylesheet" href="%s">'%i for i in csslist]) + r = template % (js, css) + return r + class Database(openerpweb.Controller): _cp_path = "/base/database" @@ -170,37 +263,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) @@ -226,31 +288,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, @@ -829,6 +872,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" 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<a.length;){if(S.call(a[c])===bx)b=b.concat(bu(a[c]));else b[b.length]=a[c]}return b}D={script:function(){be(V);var a=bu(arguments),b=D,c;if(bB){for(c=-1;++c<a.length;){if(B(a[c]))a[c]=a[c]();if(c===0){Y(function(){bt((typeof a[0]==q)?{src:a[0]}:a[0])})}else b=b.script(a[c]);b=b.wait()}}else{for(c=-1;++c<a.length;){if(B(a[c]))a[c]=a[c]()}Y(function(){for(c=-1;++c<a.length;){bt((typeof a[c]==q)?{src:a[c]}:a[c])}})}V=A(function(){s=i},5);return b},wait:function(a){be(V);s=i;if(!B(a))a=G;var b=H(t||J,l),c=b.trigger,d=function(){try{a()}catch(err){}c()};delete b.trigger;var g=function(){if(J&&!bk)bn=d;else d()};if(t&&!J)K.push(g);else Y(g);return b}};if(t){D.trigger=function(){var a,b=-1;while(a=K[++b])a();K=[]}}else D.trigger=G;return D}function bv(a){var b,c={},d={"UseCachePreload":"cache","UseLocalXHR":"xhr","UsePreloading":E,"AlwaysPreserveOrder":O,"AllowDuplicates":"dupe"},g={"AppendTo":z,"BasePath":"base"};for(b in d)g[b]=d[b];c.order=!(!v.order);for(b in g){if(g[P](b)&&v[g[b]]!=e)c[g[b]]=(a[b]!=e)?a[b]:v[g[b]]}for(b in d){if(d[P](b))c[d[b]]=!(!c[d[b]])}if(!c[E])c.cache=c.order=c.xhr=i;c.which=(c.which===w||c.which===L)?c.which:w;return c}p.$LAB={setGlobalDefaults:function(a){v=bv(a)},setOptions:function(a){return H(i,bv(a))},script:function(){return H().script.apply(e,arguments)},wait:function(){return H().wait.apply(e,arguments)}};(function(a,b,c){if(k[u]==e&&k[a]){k[u]="loading";k[a](b,c=function(){k.removeEventListener(b,c,i);k[u]=Z},i)}})("addEventListener","DOMContentLoaded")})(window);
\ No newline at end of file diff --git a/addons/base/static/src/base.html b/addons/base/static/src/base.html deleted file mode 100644 index 0c615ed8903..00000000000 --- a/addons/base/static/src/base.html +++ /dev/null @@ -1,56 +0,0 @@ -<!DOCTYPE html> -<html style="height: 100%"> -<head> - <meta http-equiv="content-type" content="text/html; charset=utf-8" /> - <title>OpenERP</title> - <link rel="shortcut icon" href="/base/static/src/img/favicon.ico" type="image/x-icon"/> - - <script type="text/javascript" src="/base/static/lib/LABjs/LAB.js"></script> - <script type="text/javascript" src="/base/static/lib/underscore/underscore.js"></script> - <script type="text/javascript" src="/base/static/lib/underscore/underscore.string.js"></script> - <script type="text/javascript" src="/base/static/lib/qweb/qweb2.js"></script> - <!-- Uncomment in order to use legacy QWeb - <script type="text/javascript" src="/base/static/lib/qweb/qweb.js"></script> - --> - <script type="text/javascript" src="/base/static/lib/jquery/jquery-1.5.2.js"></script> - <script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js"></script> - <script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-timepicker-addon.js"></script> - <script type="text/javascript" src="/base/static/lib/jquery.ui.notify/js/jquery.notify.js"></script> - <script type="text/javascript" src="/base/static/lib/jquery.superfish/js/hoverIntent.js"></script> - <script type="text/javascript" src="/base/static/lib/jquery.superfish/js/superfish.js"></script> - <script type="text/javascript" src="/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script> - <script type="text/javascript" src="/base/static/lib/jquery.contextmenu/jquery.contextmenu.r2.packed.js"></script> - <script type="text/javascript" src="/base/static/lib/datejs/date-en-US.js"></script> - <script type="text/javascript" src="/base/static/lib/json/json2.js"></script> - <script type="text/javascript" src="/base/static/src/js/base.js"></script> - <script type="text/javascript" src="/base/static/src/js/controller.js"></script> - <script type="text/javascript" src="/base/static/src/js/dates.js"></script> - <script type="text/javascript" src="/base/static/src/js/chrome.js"></script> - <script type="text/javascript" src="/base/static/src/js/data.js"></script> - <script type="text/javascript" src="/base/static/src/js/views.js"></script> - <script type="text/javascript" src="/base/static/src/js/form.js"></script> - <script type="text/javascript" src="/base/static/src/js/list.js"></script> - <script type="text/javascript" src="/base/static/src/js/list-editable.js"></script> - <script type="text/javascript" src="/base/static/src/js/search.js"></script> - <script type="text/javascript" src="/base/static/src/js/view_tree.js"></script> - - <link rel="stylesheet" type="text/css" media="screen" href="/base/static/lib/jquery.ui/css/smoothness/jquery-ui-1.8.9.custom.css" /> - <link rel="stylesheet" type="text/css" media="screen" href="/base/static/lib/jquery.ui.notify/css/ui.notify.css" /> - <link rel="stylesheet" type="text/css" href="/base/static/lib/jquery.superfish/css/superfish.css" media="screen"> - - <link rel="stylesheet" href="/base/static/src/css/base.css" type="text/css"/> - <!--[if lte IE 7]> - <link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/> - <![endif]--> - - <script type="text/javascript"> - $(function() { - QWeb = window.QWeb || new QWeb2.Engine(); - var oe = openerp.init(); - oe.base.webclient("oe"); - }); - </script> -</head> -<body id="oe" class="openerp"> -</body> -</html> diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index 6228e0c1e17..483a433eb99 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -1093,6 +1093,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/js/chrome.js b/addons/base/static/src/js/chrome.js index 75c34286a80..63226a2f9b9 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -137,63 +137,116 @@ openerp.base.Registry = openerp.base.Class.extend( /** @lends openerp.base.Regis }); /** - * 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 session aware controller + * a controller takes an already existing dom element and manage it */ -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({ +openerp.base.Controller = openerp.base.Controller.extend( /** @lends openerp.base.Controller# */{ init: function(parent, element_id) { this._super(parent, element_id); - this.$element.notify({ - speed: 500, - expires: 1500 - }); + if(this.controller_parent && this.controller_parent.session) { + this.session = this.controller_parent.session; + } }, - notify: function(title, text) { - this.$element.notify('create', { - title: title, - text: text - }); + /** + * Performs a JSON-RPC call + * + * @param {String} url endpoint url + * @param {Object} data RPC parameters + * @param {Function} success RPC call success callback + * @param {Function} error RPC call error callback + * @returns {jQuery.Deferred} deferred object for the RPC call + */ + rpc: function(url, data, success, error) { + return this.session.rpc(url, data, success, error); }, - warn: function(title, text) { - this.$element.notify('create', 'oe_notification_alert', { - title: title, - text: text - }); + do_action: function(action, on_finished) { + return this.parent.do_action(action, on_finished); + } +}); + +/** + * OpenERP session aware widget + * A widget is a controller that doesnt take an element_id + * it render its own html render() that you should insert into the dom + * and bind it at start() + */ +openerp.base.BaseWidget = openerp.base.Controller.extend({ + /** + * The name of the QWeb template that will be used for rendering. Must be + * redefined in subclasses or the render() method can not be used. + * + * @type string + */ + template: null, + /** + * The prefix used to generate an id automatically. Should be redefined in + * subclasses. If it is not defined, a default identifier will be used. + * + * @type string + */ + identifier_prefix: 'generic-identifier', + /** + * Base class for widgets. Handle rendering (based on a QWeb template), + * identifier generation, parenting and destruction of the widget. + * Also initialize the identifier. + * + * @constructs + * @params {openerp.base.search.BaseWidget} parent The parent widget. + */ + init: function (parent) { + this._super(parent); + this.make_id(this.identifier_prefix); + }, + /** + * Sets and returns a globally unique identifier for the widget. + * + * If a prefix is appended, the identifier will be appended to it. + * + * @params sections prefix sections, empty/falsy sections will be removed + */ + make_id: function () { + this.element_id = _.uniqueId(_.toArray(arguments).join('_')); + return this.element_id; + }, + /** + * Render the widget. This.template must be defined. + * The content of the current object is passed as context to the template. + * + * @param {object} additional Additional context arguments to pass to the template. + */ + render: function (additional) { + return QWeb.render(this.template, _.extend({}, this, additional != null ? additional : {})); + }, + /** + * "Starts" the widgets. Called at the end of the rendering, this allows + * to get a jQuery object referring to the DOM ($element attribute). + */ + start: function () { + this._super(); + var tmp = document.getElementById(this.element_id); + this.$element = tmp ? $(tmp) : null; + }, + /** + * "Stops" the widgets. Called when the view destroys itself, this + * lets the widgets clean up after themselves. + */ + stop: function () { + if(this.$element != null) { + this.$element.remove(); + } + this._super(); } }); -// Session should be a Class not Controller -openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.base.Session# */{ +openerp.base.Session = openerp.base.Controller.extend( /** @lends openerp.base.Session# */{ /** * @constructs - * @extends openerp.base.BasicController * @param element_id to use for exception reporting * @param server * @param port */ init: function(parent, element_id, server, port) { - this._super(element_id); + 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"; @@ -372,7 +425,7 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b for(var i=0; i<cookies.length; ++i) { var cookie = cookies[i].replace(/^\s*/, ''); if(cookie.indexOf(nameEQ) === 0) { - return decodeURIComponent(cookie.substring(nameEQ.length)); + return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length))); } } return null; @@ -388,7 +441,7 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b set_cookie: function (name, value, ttl) { ttl = ttl || 24*60*60*365; document.cookie = [ - this.element_id + '|' + name + '=' + encodeURIComponent(value), + this.element_id + '|' + name + '=' + encodeURIComponent(JSON.stringify(value)), 'max-age=' + ttl, 'expires=' + new Date(new Date().getTime() + ttl*1000).toGMTString() ].join(';'); @@ -399,15 +452,20 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b load_modules: function() { var self = this; this.rpc('/base/session/modules', {}, function(result) { - self.module_list = result['modules']; + self.module_list = result; var modules = self.module_list.join(','); - self.rpc('/base/session/csslist', {mods: modules}, self.do_load_css); - self.rpc('/base/session/jslist', {"mods": modules}, self.debug ? self.do_load_modules_debug : self.do_load_modules_prod); + if(self.debug || true) { + self.rpc('/base/webclient/csslist', {"mods": modules}, self.do_load_css); + self.rpc('/base/webclient/jslist', {"mods": modules}, self.do_load_js); + } else { + self.do_load_css(["/base/webclient/css?mods="+modules]); + self.do_load_js(["/base/webclient/js?mods="+modules]); + } openerp._modules_loaded = true; }); }, - do_load_css: function (result) { - _.each(result.files, function (file) { + do_load_css: function (files) { + _.each(files, function (file) { $('head').append($('<link>', { 'href': file, 'rel': 'stylesheet', @@ -415,16 +473,23 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b })); }); }, - do_load_modules_debug: function(result) { - $LAB.setOptions({AlwaysPreserveOrder: true}) - .script(result.files) - .wait(this.on_modules_loaded); - }, - do_load_modules_prod: function() { - // load merged ones - // /base/session/css?mod=mod1,mod2,mod3 - // /base/session/js?mod=mod1,mod2,mod3 - // use $.getScript(‘your_3rd_party-script.js’); ? i want to keep lineno ! + do_load_js: function(files) { + var self = this; + if(files.length != 0) { + var file = files.shift(); + var tag = document.createElement('script'); + tag.type = 'text/javascript'; + tag.src = file; + tag.onload = tag.onreadystatechange = function() { + if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done ) + return; + tag.onload_done = true; + self.do_load_js(files); + }; + document.head.appendChild(tag); + } else { + this.on_modules_loaded(); + } }, on_modules_loaded: function() { for(var j=0; j<this.module_list.length; j++) { @@ -441,108 +506,25 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b } }); -/** - * OpenERP session aware controller - * a controller takes an already existing dom element and manage it - */ -openerp.base.Controller = openerp.base.BasicController.extend( /** @lends openerp.base.Controller# */{ - /** - * @constructs - * @extends openerp.base.BasicController - */ +openerp.base.Notification = openerp.base.Controller.extend({ init: function(parent, element_id) { this._super(parent, element_id); - if(this.parent && this.parent.session) { - this.session = this.parent.session; - } - }, - /** - * Performs a JSON-RPC call - * - * @param {String} url endpoint url - * @param {Object} data RPC parameters - * @param {Function} success RPC call success callback - * @param {Function} error RPC call error callback - * @returns {jQuery.Deferred} deferred object for the RPC call - */ - rpc: function(url, data, success, error) { - return this.session.rpc(url, data, success, error); - }, - do_action: function(action, on_finished) { - return this.parent.do_action(action, on_finished); - } -}); - -/** - * OpenERP session aware widget - * A widget is a controller that doesnt take an element_id - * it render its own html render() that you should insert into the dom - * and bind it a start() - */ -openerp.base.BaseWidget = openerp.base.Controller.extend({ - /** - * The name of the QWeb template that will be used for rendering. Must be - * redefined in subclasses or the render() method can not be used. - * - * @type string - */ - template: null, - /** - * The prefix used to generate an id automatically. Should be redefined in - * subclasses. If it is not defined, a default identifier will be used. - * - * @type string - */ - identifier_prefix: 'generic-identifier', - /** - * Base class for widgets. Handle rendering (based on a QWeb template), - * identifier generation, parenting and destruction of the widget. - * Also initialize the identifier. - * - * @constructs - * @params {openerp.base.search.BaseWidget} parent The parent widget. - */ - init: function (parent) { - this._super(parent); - this.make_id(this.identifier_prefix); - }, - /** - * Sets and returns a globally unique identifier for the widget. - * - * If a prefix is appended, the identifier will be appended to it. - * - * @params sections prefix sections, empty/falsy sections will be removed - */ - make_id: function () { - this.element_id = _.uniqueId(_.toArray(arguments).join('_')); - return this.element_id; - }, - /** - * "Starts" the widgets. Called at the end of the rendering, this allows - * to get a jQuery object referring to the DOM ($element attribute). - */ - start: function () { - this._super(); - var tmp = document.getElementById(this.element_id); - this.$element = tmp ? $(tmp) : null; + this.$element.notify({ + speed: 500, + expires: 1500 + }); }, - /** - * "Stops" the widgets. Called when the view destroys itself, this - * lets the widgets clean up after themselves. - */ - stop: function () { - if(this.$element != null) { - this.$element.remove(); - } + notify: function(title, text) { + this.$element.notify('create', { + title: title, + text: text + }); }, - /** - * Render the widget. This.template must be defined. - * The content of the current object is passed as context to the template. - * - * @param {object} additional Additional context arguments to pass to the template. - */ - render: function (additional) { - return QWeb.render(this.template, _.extend({}, this, additional != null ? additional : {})); + warn: function(title, text) { + this.$element.notify('create', 'oe_notification_alert', { + title: title, + text: text + }); } }); @@ -550,6 +532,7 @@ openerp.base.Dialog = openerp.base.BaseWidget.extend({ dialog_title: "", identifier_prefix: 'dialog', init: function (parent, options) { + var self = this; this._super(parent); this.options = { modal: true, @@ -560,7 +543,10 @@ openerp.base.Dialog = openerp.base.BaseWidget.extend({ min_height: 0, max_height: '100%', autoOpen: false, - buttons: {} + buttons: {}, + close: function () { + self.stop(); + } }; for (var f in this) { if (f.substr(0, 10) == 'on_button_') { @@ -623,11 +609,7 @@ openerp.base.Dialog = openerp.base.BaseWidget.extend({ this.set_options(options); this.$dialog.dialog(this.options).dialog('open'); }, - close: function(options) { - this.$dialog.dialog('close'); - }, stop: function () { - this.close(); this.$dialog.dialog('destroy'); } }); @@ -1132,7 +1114,7 @@ openerp.base.WebClient = openerp.base.Controller.extend({ init: function(element_id) { this._super(null, element_id); - QWeb.add_template("xml/base.xml"); + QWeb.add_template("/base/static/src/xml/base.xml"); var params = {}; if(jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { this.$element.addClass("kitten-mode-activated"); @@ -1144,7 +1126,7 @@ openerp.base.WebClient = openerp.base.Controller.extend({ this.crashmanager = new openerp.base.CrashManager(this); this.crashmanager.start(false); - // Do you autorize this ? + // Do you autorize this ? will be replaced by notify() in controller openerp.base.Controller.prototype.notification = new openerp.base.Notification(this, "oe_notification"); this.header = new openerp.base.Header(this, "oe_header"); diff --git a/addons/base/static/src/js/controller.js b/addons/base/static/src/js/controller.js index a5c7bca4b53..c9ca3193e70 100644 --- a/addons/base/static/src/js/controller.js +++ b/addons/base/static/src/js/controller.js @@ -66,7 +66,7 @@ openerp.base.controller = function(instance) { return Class; }; -})(instance.base); +})(); // todo change john resig class to keep window clean instance.base.Class = window.Class @@ -121,14 +121,39 @@ instance.base.callback = function(obj, method) { }; /** + * Generates an inherited class that replaces all the methods by null methods (methods + * that does nothing and always return undefined). + * + * @param {Class} claz + * @param {dict} add Additional functions to override. + * @return {Class} + */ +instance.base.generate_null_object_class = function(claz, add) { + var newer = {}; + var copy_proto = function(prototype) { + for (var name in prototype) { + if(typeof prototype[name] == "function") { + newer[name] = function() {}; + } + } + if (prototype.prototype) + copy_proto(prototype.prototype); + }; + copy_proto(claz.prototype); + newer.init = instance.base.Controller.prototype.init; + var tmpclass = claz.extend(newer); + return tmpclass.extend(add || {}); +}; + +/** * OpenERP Controller + * TODO merge BaseWidget with Controller */ -instance.base.BasicController = instance.base.Class.extend( /** @lends instance.base.BasicController# */{ +instance.base.Controller = instance.base.Class.extend( /** @lends instance.base.Controller# */{ /** + * @constructs * rpc operations, event binding and callback calling should be done in - * start() instead of init so that event can be hooked in between. - * - * @constructs + * start() instead of init so that events can be hooked in between. */ init: function(parent, element_id) { this.element_id = element_id; @@ -137,12 +162,14 @@ instance.base.BasicController = instance.base.Class.extend( /** @lends instance. instance.screen[element_id] = this; } // save the parent children relationship - this.controller_parent = null; + this.controller_parent = parent; this.controller_children = []; - this.parent = parent; - if(this.parent && this.parent.children) { - this.parent.children.push(this); + if(parent && parent.controller_children) { + parent.controller_children.push(this); } + // backward compatibility + this.parent = this.controller_parent; + this.children = this.controller_children; // Transform on_* method into openerp.base.callbacks for (var name in this) { @@ -156,9 +183,8 @@ instance.base.BasicController = instance.base.Class.extend( /** @lends instance. } }, /** - * Controller start - * event binding, rpc and callback calling required to initialize the - * object can happen here + * Event binding, rpc and callback calling required to initialize the + * object should happen here * * Returns a promise object letting callers (subclasses and direct callers) * know when this component is done starting @@ -174,6 +200,12 @@ instance.base.BasicController = instance.base.Class.extend( /** @lends instance. return $.Deferred().done().promise(); }, stop: function() { + if (this.parent && this.parent.children) { + this.parent.children = _.without(this.parent.children, this); + this.parent.controller_children = this.parent.children; + } + this.parent = null; + this.controller_parent = null; }, log: function() { var args = Array.prototype.slice.call(arguments); diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index c9892ff4706..2e89dacceb8 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -240,11 +240,12 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base. * @constructs * @extends openerp.base.Controller * - * @param {openerp.base.Session} session current OpenERP session * @param {String} model the OpenERP model this dataset will manage */ - init: function(parent, model, context) { - this._super(parent); + 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; diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index b304fca8d64..e66a073643c 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -1,7 +1,7 @@ openerp.base.form = function (openerp) { openerp.base.views.add('form', 'openerp.base.FormView'); -openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{ +openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{ /** * Indicates that this view is not searchable, and thus that no search * view should be displayed (if there is one active). @@ -19,6 +19,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV */ init: function(parent, element_id, dataset, view_id, options) { this._super(parent, element_id); + this.set_default_options(); this.view_manager = parent || new openerp.base.NullViewManager(); this.dataset = dataset; this.model = dataset.model; @@ -53,10 +54,14 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV context.add(this.view_manager.action.context); } return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id, - toolbar:!!this.flags.sidebar, context: context}, this.on_loaded); + toolbar: this.options.sidebar, context: context}, this.on_loaded); } }, stop: function() { + if (this.sidebar) { + this.sidebar.attachments.stop(); + this.sidebar.stop(); + } _.each(this.widgets, function(w) { w.stop(); }); @@ -85,12 +90,16 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV $('<xmp>' + openerp.base.json_node_to_xml(self.fields_view.arch, true) + '</xmp>').dialog({ width: '95%', height: 600}); }); - if(this.view_manager.sidebar) - this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar); + if (this.options.sidebar && this.options.sidebar_id) { + this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id); + this.sidebar.start(); + this.sidebar.attachments = new openerp.base.form.SidebarAttachments(this.sidebar, this.sidebar.add_section("Attachments"), this); + this.sidebar.add_toolbar(data.fields_view.toolbar); + this.sidebar.do_unfold(); + } this.has_been_loaded.resolve(); }, do_show: function () { - var self = this; var promise; if (this.dataset.index === null) { // null index means we should start a new record @@ -98,13 +107,17 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV } else { promise = this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); } - self.$element.show(); - if(this.view_manager.sidebar) - this.view_manager.sidebar.do_refresh(true); + this.$element.show(); + if (this.sidebar) { + this.sidebar.$element.show(); + } return promise; }, do_hide: function () { this.$element.hide(); + if (this.sidebar) { + this.sidebar.$element.hide(); + } }, on_record_loaded: function(record) { if (!record) { @@ -144,7 +157,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(); } @@ -389,7 +404,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})); @@ -406,35 +423,55 @@ 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; + reload: function() { + if (this.datarecord.id) { + this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); + } else { + this.on_button_new(); } - if (!this.datarecord.id) { + }, + get_fields_values: function() { + var values = {}; + _.each(this.fields, function(value, key) { + var val = value.get_value(); + values[key] = val; + }); + return values; + } +}); + +/** @namespace */ +openerp.base.form = {}; + +openerp.base.form.SidebarAttachments = openerp.base.Controller.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 { - // TODO fme: modify this so it doesn't try to load attachments when there is not sidebar - /*(new openerp.base.DataSetSearch( - this, 'ir.attachment', this.dataset.get_context(), - [['res_model', '=', this.dataset.model], - ['res_id', '=', this.datarecord.id], + (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);*/ + 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); + 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_sidebar; + window[this.element_id + '_iframe'] = this.do_update; var $e = $(e.target); if ($e.val() != '') { - this.$sidebar.find('form.oe-binary-form').submit(); + 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(); } @@ -451,27 +488,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV 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); - } else { - this.on_button_new(); - } - }, - get_fields_values: function() { - var values = {}; - _.each(this.fields, function(value, key) { - var val = value.get_value(); - values[key] = val; - }); - return values; } }); -/** @namespace */ -openerp.base.form = {}; - openerp.base.form.compute_domain = function(expr, fields) { var stack = []; for (var i = expr.length - 1; i >= 0; i--) { @@ -1784,7 +1803,7 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({ self.on_ui_change(); }); - this.list_view = new openerp.base.form.Many2ManyListView(this.view.view_manager, 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 }); @@ -1918,7 +1937,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(this, + self.view_list = new openerp.base.form.SelectCreateListView(self, self.element_id + "_view_list", self.dataset, false, {'deletable': false}); self.view_list.popup = self; @@ -2219,8 +2238,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 6812d4fac09..a4e516721ca 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( '<progress value="%.2f" max="100.0">%.2f%%</progress>', - record.value, record.value); + value, value); + case 'many2one': + // name_get value format + return value[1]; default: - return record.value; + return value; } } }; @@ -89,6 +102,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi */ 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; @@ -254,9 +268,11 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi }) .val(self._limit || 'NaN'); }); - if(this.view_manager.sidebar) - this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar); - + if (this.options.sidebar && this.options.sidebar_id) { + this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id); + this.sidebar.start(); + this.sidebar.add_toolbar(data.fields_view.toolbar); + } }, /** * Configures the ListView pager based on the provided dataset's information @@ -348,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] - }; + }); }); }, /** @@ -386,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('.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; }, /** @@ -414,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); } }, @@ -476,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(); }); }, /** @@ -504,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) @@ -555,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)) { @@ -563,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; } }); @@ -581,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]}; } }); @@ -600,9 +642,9 @@ 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) @@ -667,7 +709,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. 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) { @@ -805,7 +847,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. 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 @@ -980,8 +1022,8 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas 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 @@ -1085,6 +1127,45 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas }); 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 = $('<tbody>'); @@ -1103,6 +1184,7 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas 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 fcf4a175b3c..8a51abe1335 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -110,9 +110,10 @@ 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(this, this.model); @@ -140,31 +141,86 @@ openerp.base.SearchView = openerp.base.Controller.extend({ return widget.start(); }).value(); - // filters management - this.$element.find(".oe_search-view-filters-management").change(this.on_filters_management); - - var self = this; $.when.apply(null, widget_starts).then(function () { self.ready.resolve(); }); + + 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 + }); } }, /** @@ -179,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 = []; @@ -205,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 @@ -593,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({ @@ -839,7 +911,9 @@ 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); + if (this.children.length >= 1) { + this.children[this.children.length - 1].set_last_group(true); + } } }); diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index 1c8b7487cd2..b98b0bc637f 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -11,13 +11,25 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ 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. + // 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({ @@ -49,6 +61,9 @@ 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) { @@ -105,7 +120,6 @@ 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; }, /** @@ -119,7 +133,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(); @@ -143,7 +162,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.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); @@ -255,13 +274,15 @@ 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(parent, element_id, action) { this.session = parent.session; @@ -282,19 +303,10 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ // 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); @@ -319,7 +331,6 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ }, stop: function() { // should be replaced by automatic destruction implemented in BaseWidget - this.sidebar.stop(); this._super(); }, /** @@ -346,62 +357,88 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ } }); -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); +openerp.base.Sidebar = openerp.base.Controller.extend({ + init: function(parent, element_id) { + this._super(parent, element_id); + this.items = {}; }, - 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 })); - + start: function() { var self = this; + this._super(this, arguments); + this.$element.html(QWeb.render('Sidebar')); this.$element.find(".toggle-sidebar").click(function(e) { - self.$element.toggleClass('open-sidebar closed-sidebar'); - e.stopPropagation(); - e.preventDefault(); + self.do_toggle(); }); - - 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(); + }, + 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[1], items); + } }); }, - start: function() { - this._super(); - this.do_refresh(false); + add_section: function(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: optionnal dom class name for the line, + // } + // Note: The item should have one action or/and a callback + var self = this, + section_id = _.uniqueId(this.element_id + '_section_'); + 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, + 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')); + 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', @@ -422,6 +459,14 @@ openerp.base.Export = openerp.base.Dialog.extend({ }); openerp.base.View = openerp.base.Controller.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 + }); + }, /** * Fetches and executes the action identified by ``action_data``. * diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index de2c9eb029e..de55e154dbc 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -246,7 +246,7 @@ <td><label for="db">Database:</label></td> <td> <t t-if="!db_list"> - <input type="text" name="db" value="trunk" autofocus="true"/> + <input type="text" name="db" t-att-value="selected_db || ''" autofocus="true"/> </t> <t t-if="db_list"> <select name="db"> @@ -328,8 +328,8 @@ <div class="company_logo" /> </a> <h1 class="header_title" t-if="session.session_is_valid()"> - <span class="company">$company</span> - (<span class="database">$database</span>)<br/> - <small class="username">$username session_id: <t t-esc="session.session_id"/></small> + <span class="database"><t t-esc="session.db"/></span> - <t t-esc="session.login"/> <br/> + <small class="username">rpc_session_id: <t t-esc="session.session_id"/></small> </h1> <div class="header_corner"> <ul class="block" t-if="session.session_is_valid()"> @@ -417,10 +417,32 @@ </t> </td> <td class="view-manager-main-sidebar" height="100%"> + <t t-foreach="views" t-as="view"> + <div t-attf-id="#{prefix}_sidebar_#{view.view_type}" class="sidebar-main-div closed-sidebar" style="display: none"/> + </t> </td> </tr> </table> </t> +<t t-name="Sidebar"> + <a class="toggle-sidebar"></a> + <div class="sidebar-content"> + <div class="sidebar-actions"> + </div> + </div> +</t> +<t t-name="Sidebar.section"> + <h2><t t-esc="name"/></h2> + <div t-att-id="section_id"> + <ul t-if="items"> + <li t-foreach="items" t-as="item" t-att-class="item.classname"> + <a class="oe_sidebar_action_a" t-att-id="item.element_id" href="#"> + <t t-esc="item.label"/> + </a> + </li> + </ul> + </div> +</t> <table t-name="ListView" class="oe-listview-content"> <t t-set="columns_count" t-value="visible_columns.length + (options.selectable ? 1 : 0) + (options.deletable ? 1 : 0)"/> <thead class="ui-widget-header"> @@ -478,7 +500,7 @@ <tr> <td t-if="options.selectable"/> <td t-foreach="aggregate_columns" t-as="column" class="oe-list-footer oe-number" - t-att-data-field="column.field" t-att-title="column.label"> + t-att-data-field="column.id" t-att-title="column.label"> </td> <td t-if="options.deletable"/> </tr> @@ -545,21 +567,20 @@ <t t-raw="frame.render()"/> </t> <t t-name="FormView.sidebar.attachments"> - <h2>Attachments</h2> <div class="oe-sidebar-attachments-toolbar"> <div class="oe-binary-file-set" style="float: right"> <form class="oe-binary-form" t-attf-target="#{element_id}_iframe" method="post" enctype="multipart/form-data" action="/base/binary/upload_attachment"> <input type="hidden" name="session_id" t-att-value="session.session_id"/> <input type="hidden" name="callback" t-attf-value="#{element_id}_iframe"/> - <input type="hidden" name="model" t-att-value="dataset.model"/> - <input type="hidden" name="id" t-att-value="datarecord.id"/> + <input type="hidden" name="model" t-att-value="view.dataset.model"/> + <input type="hidden" name="id" t-att-value="view.datarecord.id"/> <button class="button" type="button"> <img src="/base/static/src/img/throbber.gif" width="16" height="16" style="display: none"/> <span>Add</span> </button> <input type="file" class="oe-binary-file" name="ufile" title="Add attachment" - t-att-onclick="datarecord.id ? null : 'alert(\'No record selected ! You can only attach to existing record.\'); return false;'"/> + t-att-onclick="view.datarecord.id ? null : 'alert(\'No record selected ! You can only attach to existing record.\'); return false;'"/> </form> <iframe t-attf-id="#{element_id}_iframe" t-attf-name="#{element_id}_iframe" style="display: none"> </iframe> </div> @@ -569,7 +590,7 @@ <li t-foreach="attachments" t-as="attachment"> <t t-if="attachment.type == 'binary'" t-set="attachment.url" t-value="'/base/binary/saveas?session_id=' + session.session_id + '&model=ir.attachment&id=' + attachment.id - + '&field=datas' + '&fieldname=name' + '&t=' + (new Date().getTime())"/> + + '&field=datas&fieldname=name&t=' + (new Date().getTime())"/> <a class="oe-sidebar-attachments-link" t-att-href="attachment.url" target="_blank"> <t t-esc="attachment.name"/> </a> @@ -706,7 +727,7 @@ <img src="/base/static/src/img/icons/gtk-index.png"/></span> <div t-att-id="widget.cm_id" class="contextMenu" style="display:none"><ul> <li t-att-id="widget.cm_id + '_open'" style="color:grey">Open...</li> - <li t-att-id="widget.cm_id + '_create'">Create and Edit...</li> + <li t-att-id="widget.cm_id + '_create'">Create...</li> <li t-att-id="widget.cm_id + '_search'">Search...</li> </ul></div> </div> @@ -849,14 +870,28 @@ <input type="reset" value="Clear"/> <button class="oe_search-view-custom-filter-btn"><span>Advanced Filter</span></button> <select class="oe_search-view-filters-management"> - <option value="_filters">-- Filters --</option> - <option value="_actions">-- Actions --</option> - <option value="save_filter">Save Filter</option> - <option value="manage_filters">Manage Filters</option> </select> </div> </form> </t> +<t t-name="SearchView.managed-filters"> + <option value="_filters">-- Filters --</option> + <t t-set="i" t-value="0"/> + <t t-foreach="filters" t-as="filter"> + <option t-att-value="'get:' + i"><t t-esc="filter.name"/></option> + <t t-set="i" t-value="i+1"/> + </t> + <option value="_actions">-- Actions --</option> + <option value="save_filter">Save Filter</option> + <option value="manage_filters">Manage Filters</option> +</t> +<t t-name="SearchView.managed-filters.add"> + <div> + <p>Filter Name:</p> + <input type="text"/> + <p>(Any existing filter with the same name will be replaced)</p> + </div> +</t> <t t-name="SearchView.render_lines"> <table class="oe-searchview-render-line" border="0" cellspacing="0" cellpadding="0" t-foreach="lines" t-as="line"> @@ -1020,29 +1055,6 @@ </t> </select> </t> -<t t-name="ViewManager.sidebar"> - <div t-att-id="element_id" class="sidebar-main-div closed-sidebar"> - - </div> -</t> -<t t-name="ViewManager.sidebar.internal"> - <a class="toggle-sidebar"></a> - <div class="sidebar-content"> - <div class="sidebar-attachments" t-if="view == 'form'"> </div> - <div class="sidebar-actions"> - <t t-foreach="sidebar.sections" t-as="section" t-if="section.elements.length"> - <h2><t t-esc="section.label"/></h2> - <ul> - <li t-foreach="section.elements" t-as="element"> - <a class="oe_sidebar_action_a" t-attf-data-index="#{section_index}-#{element_index}" href="#"> - <t t-esc="element.name"/> - </a> - </li> - </ul> - </t> - </div> - </div> -</t> <t t-name="DialogWarning"> <table cellspacing="0" cellpadding="0" border="0" class="oe-dialog-warning"> <tr> |
