diff --git a/addons/web/common/http.py b/addons/web/common/http.py index e23a0431a7c..69220eec2d7 100644 --- a/addons/web/common/http.py +++ b/addons/web/common/http.py @@ -403,7 +403,7 @@ class Root(object): request.parameter_storage_class = werkzeug.datastructures.ImmutableDict if request.path == '/': - params = urllib.urlencode(dict(request.args, debug='')) + params = urllib.urlencode(request.args) return werkzeug.utils.redirect(self.root + '?' + params, 301)( environ, start_response) elif request.path == '/mobile': diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index c542bffffad..dd7ea970627 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -338,7 +338,8 @@ class Session(openerpweb.Controller): "session_id": req.session_id, "uid": req.session._uid, "context": ctx, - "db": req.session._db + "db": req.session._db, + "login": req.session._login } @openerpweb.jsonrequest @@ -347,7 +348,8 @@ class Session(openerpweb.Controller): return { "uid": req.session._uid, "context": req.session.get_context() if req.session._uid else False, - "db": req.session._db + "db": req.session._db, + "login": req.session._login } @openerpweb.jsonrequest diff --git a/addons/web/static/lib/jquery.tipTip/jquery.tipTip.fme.patch b/addons/web/static/lib/jquery.tipTip/jquery.tipTip.fme.patch index 62843c10b48..7af316b2f3f 100644 --- a/addons/web/static/lib/jquery.tipTip/jquery.tipTip.fme.patch +++ b/addons/web/static/lib/jquery.tipTip/jquery.tipTip.fme.patch @@ -1,5 +1,5 @@ ---- jquery.tipTip_old.js 2011-11-14 14:05:53.000000000 +0100 -+++ jquery.tipTip.js 2011-11-14 14:41:34.000000000 +0100 +--- jquery.tipTip_old.js 2011-11-14 21:40:55.000000000 +0100 ++++ jquery.tipTip.js 2011-11-15 10:09:35.000000000 +0100 @@ -31,7 +31,7 @@ fadeIn: 200, fadeOut: 200, @@ -32,3 +32,21 @@ tiptip_content.html(org_title); tiptip_holder.hide().removeAttr("class").css("margin","0"); tiptip_arrow.removeAttr("style"); +@@ -176,8 +173,15 @@ + tiptip_arrow.css({"margin-left": arrow_left+"px", "margin-top": arrow_top+"px"}); + tiptip_holder.css({"margin-left": marg_left+"px", "margin-top": marg_top+"px"}).attr("class","tip"+t_class); + +- if (timeout){ clearTimeout(timeout); } +- timeout = setTimeout(function(){ tiptip_holder.stop(true,true).fadeIn(opts.fadeIn); }, opts.delay); ++ if (timeout) { ++ clearTimeout(timeout); ++ } ++ timeout = setTimeout(function() { ++ tiptip_holder.stop(true,true); ++ if ($.contains(document.documentElement, org_elem[0])) { ++ tiptip_holder.fadeIn(opts.fadeIn); ++ } ++ }, opts.delay); + } + + function deactive_tiptip(){ diff --git a/addons/web/static/lib/jquery.tipTip/jquery.tipTip.js b/addons/web/static/lib/jquery.tipTip/jquery.tipTip.js index e2fbb832890..0b3a24d4beb 100644 --- a/addons/web/static/lib/jquery.tipTip/jquery.tipTip.js +++ b/addons/web/static/lib/jquery.tipTip/jquery.tipTip.js @@ -174,7 +174,12 @@ tiptip_holder.css({"margin-left": marg_left+"px", "margin-top": marg_top+"px"}).attr("class","tip"+t_class); if (timeout){ clearTimeout(timeout); } - timeout = setTimeout(function(){ tiptip_holder.stop(true,true).fadeIn(opts.fadeIn); }, opts.delay); + timeout = setTimeout(function() { + tiptip_holder.stop(true,true); + if ($.contains(document.documentElement, org_elem[0])) { + tiptip_holder.fadeIn(opts.fadeIn); + } + }, opts.delay); } function deactive_tiptip(){ diff --git a/addons/web/static/lib/qweb/qweb2.js b/addons/web/static/lib/qweb/qweb2.js index 34457ade4fd..a6a6da5a6d7 100644 --- a/addons/web/static/lib/qweb/qweb2.js +++ b/addons/web/static/lib/qweb/qweb2.js @@ -4,7 +4,7 @@ // TODO: t-set + t-value + children node == scoped variable ? var QWeb2 = { expressions_cache: {}, - RESERVED_WORDS: 'true,false,NaN,null,undefined,debugger,in,instanceof,new,function,return,this,typeof,eval,Math,RegExp,Array,Object,Date'.split(','), + RESERVED_WORDS: 'true,false,NaN,null,undefined,debugger,console,in,instanceof,new,function,return,this,typeof,eval,Math,RegExp,Array,Object,Date'.split(','), ACTIONS_PRECEDENCE: 'foreach,if,call,set,esc,escf,raw,rawf,js,debug,log'.split(','), WORD_REPLACEMENT: { 'and': '&&', diff --git a/addons/web/static/lib/underscore/underscore-min.js b/addons/web/static/lib/underscore/underscore-min.js new file mode 100644 index 00000000000..fadc96ecacb --- /dev/null +++ b/addons/web/static/lib/underscore/underscore-min.js @@ -0,0 +1,30 @@ +// Underscore.js 1.2.2 +// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore +(function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(b.isFunction(a.isEqual))return a.isEqual(c);if(b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return String(a)==String(c);case "[object Number]":return a=+a,c=+c,a!=a?c!=c:a==0?1/a==1/c:a==c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== +c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c, +h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,H=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&define.amd? +define("underscore",function(){return b}):s._=b;b.VERSION="1.2.2";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,c){var b=e(a,c);(d[b]||(d[b]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e< +f;){var g=e+f>>1;d(a[g])=0})})};b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=H||function(a){if(a!== +Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)? +a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=l.call(arguments)=="[object Arguments]"?function(a){return l.call(a)=="[object Arguments]"}: +function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null}; +b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),function(c){I(c,b[c]=a[c])})};var J=0;b.uniqueId=function(a){var b=J++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g, +interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g, +"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e(a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},I=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);G.call(a,this._wrapped);return u(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped, +arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this); diff --git a/addons/web/static/lib/underscore/underscore.js b/addons/web/static/lib/underscore/underscore.js index 4f56013259b..5579c07d3d3 100644 --- a/addons/web/static/lib/underscore/underscore.js +++ b/addons/web/static/lib/underscore/underscore.js @@ -1,4 +1,4 @@ -// Underscore.js 1.1.7 +// Underscore.js 1.2.2 // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the MIT license. // Portions of Underscore are inspired or borrowed from Prototype, @@ -48,19 +48,26 @@ // Create a safe reference to the Underscore object for use below. var _ = function(obj) { return new wrapper(obj); }; - // Export the Underscore object for **CommonJS**, with backwards-compatibility - // for the old `require()` API. If we're not in CommonJS, add `_` to the - // global object. - if (typeof module !== 'undefined' && module.exports) { - module.exports = _; - _._ = _; + // Export the Underscore object for **Node.js** and **"CommonJS"**, with + // backwards-compatibility for the old `require()` API. If we're not in + // CommonJS, add `_` to the global object. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else if (typeof define === 'function' && define.amd) { + // Register as a named module with AMD. + define('underscore', function() { + return _; + }); } else { // Exported as a string, for Closure Compiler "advanced" mode. root['_'] = _; } // Current version. - _.VERSION = '1.1.7'; + _.VERSION = '1.2.2'; // Collection Functions // -------------------- @@ -187,7 +194,7 @@ if (obj == null) return result; if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); each(obj, function(value, index, list) { - if (result |= iterator.call(context, value, index, list)) return breaker; + if (result || (result = iterator.call(context, value, index, list))) return breaker; }); return !!result; }; @@ -198,8 +205,8 @@ var found = false; if (obj == null) return found; if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - any(obj, function(value) { - if (found = value === target) return true; + found = any(obj, function(value) { + return value === target; }); return found; }; @@ -220,6 +227,7 @@ // Return the maximum element or (element-based computation). _.max = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); + if (!iterator && _.isEmpty(obj)) return -Infinity; var result = {computed : -Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; @@ -231,6 +239,7 @@ // Return the minimum element (or element-based computation). _.min = function(obj, iterator, context) { if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); + if (!iterator && _.isEmpty(obj)) return Infinity; var result = {computed : Infinity}; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; @@ -239,6 +248,21 @@ return result.value; }; + // Shuffle an array. + _.shuffle = function(obj) { + var shuffled = [], rand; + each(obj, function(value, index, list) { + if (index == 0) { + shuffled[0] = value; + } else { + rand = Math.floor(Math.random() * (index + 1)); + shuffled[index] = shuffled[rand]; + shuffled[rand] = value; + } + }); + return shuffled; + }; + // Sort the object's values by a criterion produced by an iterator. _.sortBy = function(obj, iterator, context) { return _.pluck(_.map(obj, function(value, index, list) { @@ -252,9 +276,11 @@ }), 'value'); }; - // Groups the object's values by a criterion produced by an iterator - _.groupBy = function(obj, iterator) { + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, val) { var result = {}; + var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; each(obj, function(value, index) { var key = iterator(value, index); (result[key] || (result[key] = [])).push(value); @@ -298,6 +324,24 @@ return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; }; + // Returns everything but the last entry of the array. Especcialy useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } + }; + // Returns everything but the first entry of the array. Aliased as `tail`. // Especially useful on the arguments object. Passing an **index** will return // the rest of the values in the array from that index onward. The **guard** @@ -306,20 +350,15 @@ return slice.call(array, (index == null) || guard ? 1 : index); }; - // Get the last element of an array. - _.last = function(array) { - return array[array.length - 1]; - }; - // Trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, function(value){ return !!value; }); }; // Return a completely flattened version of an array. - _.flatten = function(array) { + _.flatten = function(array, shallow) { return _.reduce(array, function(memo, value) { - if (_.isArray(value)) return memo.concat(_.flatten(value)); + if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); memo[memo.length] = value; return memo; }, []); @@ -333,17 +372,23 @@ // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted) { - return _.reduce(array, function(memo, el, i) { - if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; + _.uniq = _.unique = function(array, isSorted, iterator) { + var initial = iterator ? _.map(array, iterator) : array; + var result = []; + _.reduce(initial, function(memo, el, i) { + if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) { + memo[memo.length] = el; + result[result.length] = array[i]; + } return memo; }, []); + return result; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { - return _.uniq(_.flatten(arguments)); + return _.uniq(_.flatten(arguments, true)); }; // Produce an array that contains every item shared between all the @@ -391,7 +436,6 @@ return -1; }; - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. _.lastIndexOf = function(array, item) { if (array == null) return -1; @@ -426,15 +470,25 @@ // Function (ahem) Functions // ------------------ + // Reusable constructor function for prototype setting. + var ctor = function(){}; + // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Binding with arguments is also known as `curry`. // Delegates to **ECMAScript 5**'s native `Function.bind` if available. // We check for `func.bind` first, to fail fast when `func` is undefined. - _.bind = function(func, obj) { + _.bind = function bind(func, context) { + var bound, args; if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - var args = slice.call(arguments, 2); - return function() { - return func.apply(obj, args.concat(slice.call(arguments))); + if (!_.isFunction(func)) throw new TypeError; + args = slice.call(arguments, 2); + return bound = function() { + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); + ctor.prototype = func.prototype; + var self = new ctor; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) return result; + return self; }; }; @@ -470,31 +524,43 @@ return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); }; - // Internal function used to implement `_.throttle` and `_.debounce`. - var limit = function(func, wait, debounce) { - var timeout; - return function() { - var context = this, args = arguments; - var throttler = function() { - timeout = null; - func.apply(context, args); - }; - if (debounce) clearTimeout(timeout); - if (debounce || !timeout) timeout = setTimeout(throttler, wait); - }; - }; - // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. _.throttle = function(func, wait) { - return limit(func, wait, false); + var context, args, timeout, throttling, more; + var whenDone = _.debounce(function(){ more = throttling = false; }, wait); + return function() { + context = this; args = arguments; + var later = function() { + timeout = null; + if (more) func.apply(context, args); + whenDone(); + }; + if (!timeout) timeout = setTimeout(later, wait); + if (throttling) { + more = true; + } else { + func.apply(context, args); + } + whenDone(); + throttling = true; + }; }; // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. _.debounce = function(func, wait) { - return limit(func, wait, true); + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + func.apply(context, args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; }; // Returns a function that will be executed at most one time, no matter how @@ -533,12 +599,12 @@ // Returns a function that will only be executed after being called N times. _.after = function(times, func) { + if (times <= 0) return func(); return function() { if (--times < 1) { return func.apply(this, arguments); } }; }; - // Object Functions // ---------------- @@ -588,6 +654,7 @@ // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { + if (!_.isObject(obj)) return obj; return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }; @@ -599,47 +666,103 @@ return obj; }; - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - // Check object identity. - if (a === b) return true; - // Different types? - var atype = typeof(a), btype = typeof(b); - if (atype != btype) return false; - // Basic equality test (watch out for coercions). - if (a == b) return true; - // One is falsy and the other truthy. - if ((!a && b) || (a && !b)) return false; + // Internal recursive comparison function. + function eq(a, b, stack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; // Unwrap any wrapped objects. if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; - // One of them implements an isEqual()? - if (a.isEqual) return a.isEqual(b); - if (b.isEqual) return b.isEqual(a); - // Check dates' integer values. - if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); - // Both are NaN? - if (_.isNaN(a) && _.isNaN(b)) return false; - // Compare regular expressions. - if (_.isRegExp(a) && _.isRegExp(b)) - return a.source === b.source && - a.global === b.global && - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - // If a is not an object by this point, we can't handle it. - if (atype !== 'object') return false; - // Check for different array lengths before comparing contents. - if (a.length && (a.length !== b.length)) return false; - // Nothing else worked, deep compare the contents. - var aKeys = _.keys(a), bKeys = _.keys(b); - // Different object sizes? - if (aKeys.length != bKeys.length) return false; - // Recursive comparison of contents. - for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; - return true; + // Invoke a custom `isEqual` method if one is provided. + if (_.isFunction(a.isEqual)) return a.isEqual(b); + if (_.isFunction(b.isEqual)) return b.isEqual(a); + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return String(a) == String(b); + case '[object Number]': + a = +a; + b = +b; + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = stack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (stack[length] == a) return true; + } + // Add the first object to the stack of traversed objects. + stack.push(a); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + // Ensure commutative equality for sparse arrays. + if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; + } + } + } else { + // Objects with different constructors are not equivalent. + if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false; + // Deep compare objects. + for (var key in a) { + if (hasOwnProperty.call(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (hasOwnProperty.call(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + stack.pop(); + return result; + } + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b, []); }; - // Is a given array or object empty? + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; @@ -654,7 +777,7 @@ // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) === '[object Array]'; + return toString.call(obj) == '[object Array]'; }; // Is a given variable an object? @@ -663,44 +786,50 @@ }; // Is a given variable an arguments object? - _.isArguments = function(obj) { - return !!(obj && hasOwnProperty.call(obj, 'callee')); - }; + if (toString.call(arguments) == '[object Arguments]') { + _.isArguments = function(obj) { + return toString.call(obj) == '[object Arguments]'; + }; + } else { + _.isArguments = function(obj) { + return !!(obj && hasOwnProperty.call(obj, 'callee')); + }; + } // Is a given value a function? _.isFunction = function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); + return toString.call(obj) == '[object Function]'; }; // Is a given value a string? _.isString = function(obj) { - return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); + return toString.call(obj) == '[object String]'; }; // Is a given value a number? _.isNumber = function(obj) { - return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); + return toString.call(obj) == '[object Number]'; }; - // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript - // that does not equal itself. + // Is the given value `NaN`? _.isNaN = function(obj) { + // `NaN` is the only value for which `===` is not reflexive. return obj !== obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { - return obj === true || obj === false; + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; }; // Is a given value a date? _.isDate = function(obj) { - return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); + return toString.call(obj) == '[object Date]'; }; // Is the given value a regular expression? _.isRegExp = function(obj) { - return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); + return toString.call(obj) == '[object RegExp]'; }; // Is a given value equal to null? @@ -733,6 +862,11 @@ for (var i = 0; i < n; i++) iterator.call(context, i); }; + // Escape a string for HTML interpolation. + _.escape = function(string) { + return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); + }; + // Add your own custom functions to the Underscore object, ensuring that // they're correctly added to the OOP wrapper as well. _.mixin = function(obj) { @@ -753,7 +887,8 @@ // following template settings to use alternative delimiters. _.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g }; // JavaScript micro-templating, similar to John Resig's implementation. @@ -765,19 +900,22 @@ 'with(obj||{}){__p.push(\'' + str.replace(/\\/g, '\\\\') .replace(/'/g, "\\'") + .replace(c.escape, function(match, code) { + return "',_.escape(" + code.replace(/\\'/g, "'") + "),'"; + }) .replace(c.interpolate, function(match, code) { return "'," + code.replace(/\\'/g, "'") + ",'"; }) .replace(c.evaluate || null, function(match, code) { return "');" + code.replace(/\\'/g, "'") - .replace(/[\r\n\t]/g, ' ') + "__p.push('"; + .replace(/[\r\n\t]/g, ' ') + ";__p.push('"; }) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') + "');}return __p.join('');"; - var func = new Function('obj', tmpl); - return data ? func(data) : func; + var func = new Function('obj', '_', tmpl); + return data ? func(data, _) : function(data) { return func(data, _) }; }; // The OOP Wrapper @@ -836,4 +974,4 @@ return this._wrapped; }; -})(); +}).call(this); diff --git a/addons/web/static/lib/underscore/underscore.string.js b/addons/web/static/lib/underscore/underscore.string.js index d15c1f1f7ce..99966c3f475 100644 --- a/addons/web/static/lib/underscore/underscore.string.js +++ b/addons/web/static/lib/underscore/underscore.string.js @@ -1,20 +1,14 @@ // Underscore.string // (c) 2010 Esa-Matti Suuronen // Underscore.strings is freely distributable under the terms of the MIT license. -// Documentation: https://github.com/edtsech/underscore.string +// Documentation: https://github.com/epeli/underscore.string // Some code is borrowed from MooTools and Alexandru Marasteanu. -// Version 1.1.6 - +// Version 1.2.0 (function(root){ 'use strict'; - if (typeof _ != 'undefined') { - var _reverse = _().reverse, - _include = _.include; - } - // Defining helper functions. var nativeTrim = String.prototype.trim; @@ -22,7 +16,7 @@ var parseNumber = function(source) { return source * 1 || 0; }; var strRepeat = function(i, m) { - for (var o = []; m > 0; o[--m] = i); + for (var o = []; m > 0; o[--m] = i) {} return o.join(''); }; @@ -174,6 +168,8 @@ var _s = { + VERSION: '1.2.0', + isBlank: sArgs(function(str){ return (/^\s*$/).test(str); }), @@ -235,18 +231,10 @@ return arr.join(''); }), - includes: sArgs(function(str, needle){ + include: sArgs(function(str, needle){ return str.indexOf(needle) !== -1; }), - include: function(obj, needle) { - if (!_include || (/string|number/).test(typeof obj)) { - return this.includes(obj, needle); - } else { - return _include(obj, needle); - } - }, - join: sArgs(function(sep) { var args = slice(arguments); return args.join(args.shift()); @@ -256,13 +244,9 @@ return str.split("\n"); }), - reverse: function(obj){ - if (!_reverse || (/string|number/).test(typeof obj)) { - return Array.prototype.reverse.apply(String(obj).split('')).join(''); - } else { - return _reverse.call(_(obj)); - } - }, + reverse: sArgs(function(str){ + return Array.prototype.reverse.apply(String(str).split('')).join(''); + }), splice: sArgs(function(str, i, howmany, substr){ var arr = str.split(''); @@ -309,6 +293,10 @@ return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase(); }, + humanize: function(str){ + return _s.capitalize(this.underscored(str).replace(/_id$/,'').replace(/_/g, ' ')); + }, + trim: sArgs(function(str, characters){ if (!characters && nativeTrim) { return nativeTrim.call(str); @@ -333,6 +321,27 @@ return str.length > length ? str.slice(0,length) + truncateStr : str; }), + /** + * _s.prune: a more elegant version of truncate + * prune extra chars, never leaving a half-chopped word. + * @author github.com/sergiokas + */ + prune: sArgs(function(str, length, pruneStr){ + pruneStr = pruneStr || '...'; + length = parseNumber(length); + var pruned = ''; + + // Check if we're in the middle of a word + if( str.substring(length-1, length+1).search(/^\w\w$/) === 0 ) + pruned = _s.rtrim(str.slice(0,length).replace(/([\W][\w]*)$/,'')); + else + pruned = _s.rtrim(str.slice(0,length)); + + pruned = pruned.replace(/\W+$/,''); + + return (pruned.length+pruneStr.length>str.length) ? str : pruned + pruneStr; + }), + words: function(str, delimiter) { return String(str).split(delimiter || " "); }, @@ -409,31 +418,51 @@ strLeftBack: sArgs(function(sourceStr, sep){ var pos = sourceStr.lastIndexOf(sep); return (pos != -1) ? sourceStr.slice(0, pos) : sourceStr; - }) + }), + + exports: function() { + var result = {}; + + for (var prop in this) { + if (!this.hasOwnProperty(prop) || prop == 'include' || prop == 'contains' || prop == 'reverse') continue; + result[prop] = this[prop]; + } + + return result; + } }; // Aliases - _s.strip = _s.trim; - _s.lstrip = _s.ltrim; - _s.rstrip = _s.rtrim; - _s.center = _s.lrpad; - _s.ljust = _s.lpad; - _s.rjust = _s.rpad; + _s.strip = _s.trim; + _s.lstrip = _s.ltrim; + _s.rstrip = _s.rtrim; + _s.center = _s.lrpad; + _s.ljust = _s.lpad; + _s.rjust = _s.rpad; + _s.contains = _s.include; // CommonJS module is defined - if (typeof module !== 'undefined' && module.exports) { - // Export module - module.exports = _s; + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + // Export module + module.exports = _s; + } + exports._s = _s; // Integrate with Underscore.js } else if (typeof root._ !== 'undefined') { - root._.mixin(_s); + // root._.mixin(_s); + root._.string = _s; + root._.str = root._.string; // Or define it } else { - root._ = _s; + root._ = { + string: _s, + str: _s + }; } }(this || window)); diff --git a/addons/web/static/lib/underscore/underscore.string.min.js b/addons/web/static/lib/underscore/underscore.string.min.js new file mode 100644 index 00000000000..dfc1b46b082 --- /dev/null +++ b/addons/web/static/lib/underscore/underscore.string.min.js @@ -0,0 +1,14 @@ +(function(k){var o=String.prototype.trim,l=function(a,b){for(var c=[];b>0;c[--b]=a);return c.join("")},d=function(a){return function(){for(var b=Array.prototype.slice.call(arguments),c=0;c=0?"+"+f:f;i=g[4]?g[4]=="0"?"0":g[4].charAt(1):" ";k=g[6]-String(f).length;i=g[6]?l(i,k):"";j.push(g[5]?f+i:i+f)}return j.join("")};b.cache= +{};b.parse=function(a){for(var b=[],e=[],d=0;a;){if((b=/^[^\x25]+/.exec(a))!==null)e.push(b[0]);else if((b=/^\x25{2}/.exec(a))!==null)e.push("%");else if((b=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(a))!==null){if(b[2]){d|=1;var f=[],j=b[2],h=[];if((h=/^([a-z_][a-z_\d]*)/i.exec(j))!==null)for(f.push(h[1]);(j=j.substring(h[0].length))!=="";)if((h=/^\.([a-z_][a-z_\d]*)/i.exec(j))!==null)f.push(h[1]);else if((h=/^\[(\d+)\]/.exec(j))!==null)f.push(h[1]); +else throw"[_.sprintf] huh?";else throw"[_.sprintf] huh?";b[2]=f}else d|=2;if(d===3)throw"[_.sprintf] mixing positional and named placeholders is not (yet) supported";e.push(b)}else throw"[_.sprintf] huh?";a=a.substring(b[0].length)}return e};return b}(),e={VERSION:"1.2.0",isBlank:d(function(a){return/^\s*$/.test(a)}),stripTags:d(function(a){return a.replace(/<\/?[^>]+>/ig,"")}),capitalize:d(function(a){return a.charAt(0).toUpperCase()+a.substring(1).toLowerCase()}),chop:d(function(a,b){for(var b= +b*1||0||a.length,c=[],e=0;e=0&&c++,d=d+(e>=0?e:0)+b.length;return c}),chars:d(function(a){return a.split("")}),escapeHTML:d(function(a){return a.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}),unescapeHTML:d(function(a){return a.replace(/</g,"<").replace(/>/g, +">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}),escapeRegExp:d(function(a){return a.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1")}),insert:d(function(a,b,c){a=a.split("");a.splice(b*1||0,0,c);return a.join("")}),include:d(function(a,b){return a.indexOf(b)!==-1}),join:d(function(a){var b=Array.prototype.slice.call(arguments);return b.join(b.shift())}),lines:d(function(a){return a.split("\n")}),reverse:d(function(a){return Array.prototype.reverse.apply(String(a).split("")).join("")}), +splice:d(function(a,b,c,e){a=a.split("");a.splice(b*1||0,c*1||0,e);return a.join("")}),startsWith:d(function(a,b){return a.length>=b.length&&a.substring(0,b.length)===b}),endsWith:d(function(a,b){return a.length>=b.length&&a.substring(a.length-b.length)===b}),succ:d(function(a){var b=a.split("");b.splice(a.length-1,1,String.fromCharCode(a.charCodeAt(a.length-1)+1));return b.join("")}),titleize:d(function(a){for(var a=a.split(" "),b,c=0;cb?a.slice(0,b)+(c||"..."):a}),prune:d(function(a,b,c){var c=c||"...",b=b*1||0,d="",d=a.substring(b- +1,b+1).search(/^\w\w$/)===0?e.rtrim(a.slice(0,b).replace(/([\W][\w]*)$/,"")):e.rtrim(a.slice(0,b)),d=d.replace(/\W+$/,"");return d.length+c.length>a.length?a:d+c}),words:function(a,b){return String(a).split(b||" ")},pad:d(function(a,b,c,e){var d="",d=0,b=b*1||0;c?c.length>1&&(c=c.charAt(0)):c=" ";switch(e){case "right":d=b-a.length;d=l(c,d);a+=d;break;case "both":d=b-a.length;d={left:l(c,Math.ceil(d/2)),right:l(c,Math.floor(d/2))};a=d.left+a+d.right;break;default:d=b-a.length,d=l(c,d),a=d+a}return a}), +lpad:function(a,b,c){return e.pad(a,b,c)},rpad:function(a,b,c){return e.pad(a,b,c,"right")},lrpad:function(a,b,c){return e.pad(a,b,c,"both")},sprintf:m,vsprintf:function(a,b){b.unshift(a);return m.apply(null,b)},toNumber:function(a,b){var c;c=(a*1||0).toFixed(b*1||0)*1||0;return!(c===0&&a!=="0"&&a!==0)?c:Number.NaN},strRight:d(function(a,b){var c=!b?-1:a.indexOf(b);return c!=-1?a.slice(c+b.length,a.length):a}),strRightBack:d(function(a,b){var c=!b?-1:a.lastIndexOf(b);return c!=-1?a.slice(c+b.length, +a.length):a}),strLeft:d(function(a,b){var c=!b?-1:a.indexOf(b);return c!=-1?a.slice(0,c):a}),strLeftBack:d(function(a,b){var c=a.lastIndexOf(b);return c!=-1?a.slice(0,c):a}),exports:function(){var a={},b;for(b in this)if(this.hasOwnProperty(b)&&!(b=="include"||b=="contains"||b=="reverse"))a[b]=this[b];return a}};e.strip=e.trim;e.lstrip=e.ltrim;e.rstrip=e.rtrim;e.center=e.lrpad;e.ljust=e.lpad;e.rjust=e.rpad;e.contains=e.include;if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)module.exports= +e;exports._s=e}else typeof k._!=="undefined"?(k._.string=e,k._.str=k._.string):k._={string:e,str:e}})(this||window); diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index c061353d113..d8f31af1d35 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -809,33 +809,33 @@ label.error { also on the first and last children of the first and last row */ .openerp .oe-listview table.oe-listview-content { - -webkit-border-radius: 7px; - -moz-border-radius: 7px; - border-radius: 7px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; } .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; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; } .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; + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; } .openerp .oe-listview table.oe-listview-content tfoot tr:last-child th:first-child, .openerp .oe-listview table.oe-listview-content tfoot tr:last-child 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; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; } .openerp .oe-listview table.oe-listview-content tfoot tr:last-child th:last-child, .openerp .oe-listview table.oe-listview-content tfoot tr:last-child 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; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + border-bottom-right-radius: 4px; } /* Notebook */ @@ -967,20 +967,25 @@ label.error { .openerp .oe_forms textarea { resize:vertical; } -.openerp .oe_forms input[type="text"], .openerp .oe_forms input[type="password"], .openerp .oe_forms select, .openerp .oe_forms .button { +.openerp .oe_forms input[type="text"], .openerp .oe_forms input[type="password"], .openerp .oe_forms select, .openerp .oe_forms .oe_button { height: 22px; } .openerp .oe_forms input.field_datetime { min-width: 11em; } -.openerp .oe_forms.oe_frame .oe_datepicker_root { - width: 100%; -} -.openerp .oe_forms .button { +.openerp .oe_forms .oe_button { color: #4c4c4c; white-space: nowrap; + min-width: 100%; + width: 100%; } -.openerp .oe_forms .button span { +@-moz-document url-prefix() { + /* Strange firefox behaviour on width: 100% + white-space: nowrap */ + .openerp .oe_forms .oe_button { + width: auto; + } +} +.openerp .oe_forms .oe_button span { position: relative; vertical-align: top; } @@ -989,13 +994,20 @@ label.error { cursor: pointer; right: 5px; top: 3px; + z-index: 2; +} +.openerp .oe_datepicker_container { + position: absolute; + top: 0; + right: 0; + display: none; } .openerp .oe_datepicker_root { position: relative; display: inline-block; } -.openerp .oe_datepicker_root input[type="text"] { - min-width: 160px; +.openerp .oe_forms.oe_frame .oe_datepicker_root { + width: 100%; } .openerp .oe_input_icon_disabled { position: absolute; @@ -1003,7 +1015,7 @@ label.error { opacity: 0.5; filter:alpha(opacity=50); right: 5px; - top: 5px; + top: 3px; } .openerp img.oe_field_translate { margin-left: -21px; diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 25b73ee59d5..2b838ee3743 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -158,7 +158,7 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ }, on_managed_error: function(error) { $('
' + QWeb.render('DialogWarning', {error: error}) + '
').dialog({ - title: "OpenERP " + _.capitalize(error.type), + title: "OpenERP " + _.str.capitalize(error.type), buttons: { Ok: function() { $(this).dialog("close"); @@ -168,7 +168,7 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ }, on_traceback: function(error) { var dialog = new openerp.web.Dialog(this, { - title: "OpenERP " + _.capitalize(error.type), + title: "OpenERP " + _.str.capitalize(error.type), autoOpen: true, width: '90%', height: '90%', @@ -184,7 +184,8 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ } }); -openerp.web.Loading = openerp.web.Widget.extend(/** @lends openerp.web.Loading# */{ +openerp.web.Loading = openerp.web.Widget.extend(/** @lends openerp.web.Loading# */{ + template: 'Loading', /** * @constructs openerp.web.Loading * @extends openerp.web.Widget @@ -192,8 +193,8 @@ openerp.web.Loading = openerp.web.Widget.extend(/** @lends openerp.web.Loading# * @param parent * @param element_id */ - init: function(parent, element_id) { - this._super(parent, element_id); + init: function(parent) { + this._super(parent); this.count = 0; this.blocked_ui = false; this.session.on_rpc_request.add_first(this.on_rpc_event, 1); @@ -210,12 +211,13 @@ openerp.web.Loading = openerp.web.Widget.extend(/** @lends openerp.web.Loading# } this.count += increment; - if (this.count) { + if (this.count > 0) { //this.$element.html(QWeb.render("Loading", {})); this.$element.html("Loading ("+this.count+")"); this.$element.show(); this.widget_parent.$element.addClass('loading'); } else { + this.count = 0; clearTimeout(this.long_running_timer); // Don't unblock if blocked by somebody else if (self.blocked_ui) { @@ -242,10 +244,8 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database this.$option_id = $('#' + option_id); }, start: function() { + this._super(); this.$element.html(QWeb.render("Database", this)); - this.$element.closest(".openerp") - .removeClass("login-mode") - .addClass("database_block"); var self = this; var fetch_db = this.rpc("/web/database/get_list", {}, function(result) { @@ -266,23 +266,30 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database this.$element.find('#db-restore').click(this.do_restore); this.$element.find('#db-change-password').click(this.do_change_password); this.$element.find('#back-to-login').click(function() { - self.stop(); + self.hide(); }); }, stop: function () { + this.hide(); this.$option_id.empty(); this.$element .find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login') .unbind('click') .end() - .closest(".openerp") - .addClass("login-mode") - .removeClass("database_block") - .end() .empty(); this._super(); }, + show: function () { + this.$element.closest(".openerp") + .removeClass("login-mode") + .addClass("database_block"); + }, + hide: function () { + this.$element.closest(".openerp") + .addClass("login-mode") + .removeClass("database_block") + }, /** * Converts a .serializeArray() result into a dict. Does not bother folding * multiple identical keys into an array, last key wins. @@ -368,6 +375,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database } self.db_list.push(self.to_object(fields)['db_name']); self.db_list.sort(); + self.widget_parent.set_db_list(self.db_list); var form_obj = self.to_object(fields); self.wait_for_newdb(result, { password: form_obj['super_admin_pwd'], @@ -397,6 +405,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database } $db_list.find(':selected').remove(); self.db_list.splice(_.indexOf(self.db_list, db, true), 1); + self.widget_parent.set_db_list(self.db_list); self.do_notify("Dropping database", "The database '" + db + "' has been dropped"); }); } @@ -529,16 +538,16 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ var self = this; this.database = new openerp.web.Database( this, "oe_database", "oe_db_options"); + this.database.start(); this.$element.find('#oe-db-config').click(function() { - self.database.start(); + self.database.show(); }); this.$element.find("form").submit(this.on_submit); this.rpc("/web/database/get_list", {}, function(result) { - var tpl = openerp.web.qweb.render('Login_dblist', {db_list: result.db_list, selected_db: self.selected_db}); - self.$element.find("input[name=db]").replaceWith(tpl) + self.set_db_list(result.db_list); }, function(error, event) { if (error.data.fault_code === 'AccessDenied') { @@ -547,6 +556,15 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ }); }, + stop: function () { + this.database.stop(); + this._super(); + }, + set_db_list: function (list) { + this.$element.find("[name=db]").replaceWith( + openerp.web.qweb.render('Login_dblist', { + db_list: list, selected_db: this.selected_db})) + }, on_login_invalid: function() { this.$element.closest(".openerp").addClass("login-mode"); }, @@ -966,7 +984,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie this.$element.html(QWeb.render("Interface", params)); this.notification = new openerp.web.Notification(this); - this.loading = new openerp.web.Loading(this,"oe_loading"); + this.loading = new openerp.web.Loading(this); this.crashmanager = new openerp.web.CrashManager(); this.header = new openerp.web.Header(this); @@ -990,6 +1008,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie start: function() { this._super.apply(this, arguments); this.notification.prependTo(this.$element); + this.loading.appendTo($('#oe_loading')); this.header.appendTo($("#oe_header")); this.session.start(); this.login.appendTo($('#oe_login')); diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js index 369e1191665..a9731ef5b16 100644 --- a/addons/web/static/src/js/core.js +++ b/addons/web/static/src/js/core.js @@ -125,7 +125,11 @@ openerp.web.callback = function(obj, method) { callback.callback_chain.splice(i, 1); i -= 1; } - r = c.callback.apply(c.self, c.args.concat(args)); + var result = c.callback.apply(c.self, c.args.concat(args)); + if (c.callback === method) { + // return the result of the original method + r = result; + } // TODO special value to stop the chain // openerp.web.callback_stop } @@ -354,6 +358,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. this.debug = (window.location.search.indexOf('?debug') !== -1); this.session_id = false; this.uid = false; + this.username = false; this.user_context= {}; this.db = false; this.module_list = []; @@ -484,10 +489,13 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. var self = this; var params = { db: db, login: login, password: password }; return this.rpc("/web/session/login", params, function(result) { - self.session_id = result.session_id; - self.uid = result.uid; - self.user_context = result.context; - self.db = result.db; + _.extend(self, { + session_id: result.session_id, + uid: result.uid, + user_context: result.context, + db: result.db, + username: result.login + }); self.session_save(); self.on_session_valid(); return true; @@ -501,9 +509,12 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. var self = this; this.session_id = this.get_cookie('session_id'); return this.rpc("/web/session/get_session_info", {}).then(function(result) { - self.uid = result.uid; - self.user_context = result.context; - self.db = result.db; + _.extend(self, { + uid: result.uid, + user_context: result.context, + db: result.db, + username: result.login + }); if (self.uid) self.on_session_valid(); else @@ -716,7 +727,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. if (parseInt(cookie_val, 10) !== token) { continue; } // clear cookie - document.cookie = _.sprintf("%s=;expires=%s;path=/", + document.cookie = _.str.sprintf("%s=;expires=%s;path=/", cookie_name, new Date().toGMTString()); if (options.success) { options.success(); } complete(); @@ -1055,7 +1066,7 @@ openerp.web.qweb.format_text_node = function(s) { if (translation && translation.value === 'off') { return s; } - var ts = _.trim(s); + var ts = _.str.trim(s); if (ts.length === 0) { return s; } diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index f4509c82a81..69d8fd9ddfd 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -119,17 +119,18 @@ openerp.web.ContainerDataGroup = openerp.web.DataGroup.extend( /** @lends opener aggregates[key] = value || 0; }); + var group_size = fixed_group[field_name + '_count'] || fixed_group.__count || 0; + var leaf_group = fixed_group.__context.group_by.length === 0; return { __context: fixed_group.__context, __domain: fixed_group.__domain, grouped_on: field_name, // if terminal group (or no group) and group_by_no_leaf => use group.__count - length: fixed_group[field_name + '_count'] || fixed_group.__count, + length: group_size, value: fixed_group[field_name], - - openable: !(this.context['group_by_no_leaf'] - && fixed_group.__context.group_by.length === 0), + // A group is openable if it's not a leaf in group_by_no_leaf mode + openable: !(leaf_group && this.context['group_by_no_leaf']), aggregates: aggregates }; @@ -315,7 +316,7 @@ openerp.web.DataSet = openerp.web.Widget.extend( /** @lends openerp.web.DataSet def.reject(); } else { fields = fields || false; - return this.read_ids([this.ids[this.index]], fields).then(function(records) { + this.read_ids([this.ids[this.index]], fields).then(function(records) { def.resolve(records[0]); }, function() { def.reject.apply(def, arguments); diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index c4af2d0cf6b..3151b34636b 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -76,7 +76,13 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) { if (typeof value === 'number' && isNaN(value)) { value = false; } + //noinspection FallthroughInSwitchStatementJS switch (value) { + case '': + if (descriptor.type === 'char') { + return ''; + } + console.warn('Field', descriptor, 'had an empty string as value, treating as false...'); case false: case Infinity: case -Infinity: @@ -86,18 +92,18 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) { switch (descriptor.widget || descriptor.type) { case 'integer': return openerp.web.insert_thousand_seps( - _.sprintf('%d', value)); + _.str.sprintf('%d', value)); case 'float': var precision = descriptor.digits ? descriptor.digits[1] : 2; - var formatted = _.sprintf('%.' + precision + 'f', value).split('.'); + var formatted = _.str.sprintf('%.' + precision + 'f', value).split('.'); formatted[0] = openerp.web.insert_thousand_seps(formatted[0]); return formatted.join(l10n.decimal_point); case 'float_time': - return _.sprintf("%02d:%02d", + return _.str.sprintf("%02d:%02d", Math.floor(value), Math.round((value % 1) * 60)); case 'progressbar': - return _.sprintf( + return _.str.sprintf( '%.2f%%', value, value); case 'many2one': diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 4fb5f02c690..f95801975b5 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -41,7 +41,11 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search if (this.headless) { this.ready.resolve(); } else { - this.rpc("/web/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); + this.rpc("/web/searchview/load", { + model: this.model, + view_id: this.view_id, + context: this.dataset.get_context() + }, this.on_loaded); } return this.ready.promise(); }, @@ -137,7 +141,7 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search on_loaded: function(data) { if (data.fields_view.type !== 'search' || data.fields_view.arch.tag !== 'search') { - throw new Error(_.sprintf( + throw new Error(_.str.sprintf( "Got non-search view after asking for a search view: type %s, arch root %s", data.fields_view.type, data.fields_view.arch.tag)); } @@ -425,7 +429,7 @@ openerp.web.search.Invalid = openerp.web.Class.extend( /** @lends openerp.web.se this.message = message; }, toString: function () { - return _.sprintf( + return _.str.sprintf( _t("Incorrect value for field %(fieldname)s: [%(value)s] is %(message)s"), {fieldname: this.field, value: this.value, message: this.message} ); diff --git a/addons/web/static/src/js/view_editor.js b/addons/web/static/src/js/view_editor.js index 033c2a31850..f907612bc1c 100644 --- a/addons/web/static/src/js/view_editor.js +++ b/addons/web/static/src/js/view_editor.js @@ -18,7 +18,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ init_view_editor: function() { var self = this; var action = { - name: _.sprintf("Manage Views (%s)", this.model), + name: _.str.sprintf("Manage Views (%s)", this.model), context: this.session.user_context, domain: [["model", "=", this.model]], res_model: 'ir.ui.view', @@ -78,7 +78,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ var self = this; this.create_view_dialog = new openerp.web.Dialog(this, { modal: true, - title: _.sprintf("Create a view (%s)", self.model), + title: _.str.sprintf("Create a view (%s)", self.model), width: 500, height: 400, buttons: { @@ -86,16 +86,16 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ var view_values = {}; var warn = false; _.each(self.create_view_widget, function(widget) { - if (widget.invalid) { + if (widget.is_invalid) { warn = true; return false; }; - if (widget.dirty && !widget.invalid) { + if (widget.dirty && !widget.is_invalid) { view_values[widget.name] = widget.get_value(); } }); if (warn) { - self.on_valid_create_view(); + self.on_valid_create_view(self.create_view_widget); } else { $.when(self.do_save_view(view_values)).then(function() { self.create_view_dialog.close(); @@ -112,15 +112,11 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ this.create_view_dialog.start().open(); var view_widget = [{'name': 'view_name', 'string':'View Name', 'type': 'char', 'required': true, 'value' : this.model + '.custom_' + Math.round(Math.random() * 1000)}, {'name': 'view_type', 'string': 'View Type', 'type': 'selection', 'required': true, 'value': 'Form', 'selection': [['',''],['tree', 'Tree'],['form', 'Form'],['graph', 'Graph'],['calendar', 'Calender']]}, - {'name': 'proirity', 'string': 'Priority', 'type': 'char', 'required': true, 'value':'16'}]; + {'name': 'proirity', 'string': 'Priority', 'type': 'float', 'required': true, 'value':'16'}]; this.create_view_dialog.$element.append('
'); this.create_view_widget = []; _.each(view_widget, function(widget) { - var type_widget = new (self.property.get_any([widget.type])) (self.create_view_dialog, widget.name); - if (widget.selection) { - type_widget.selection = widget.selection; - } - type_widget.required = widget.required; + var type_widget = new (self.property.get_any([widget.type])) (self.create_view_dialog, widget); self.create_view_dialog.$element.find('table[id=create_view]').append('' + widget.string + ':' + type_widget.render()+''); var value = null; if (widget.value) { @@ -147,7 +143,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ if (field_name) { model_dataset.read_slice(['name','field_id'], {"domain": [['model','=',self.model]]}, function(records) { if (records) {view_string = records[0].name;} - var arch = _.sprintf("\n<%s string='%s'>\n\t\n", values.view_type, view_string, field_name, values.view_type); + var arch = _.str.sprintf("\n<%s string='%s'>\n\t\n", values.view_type, view_string, field_name, values.view_type); var vals = {'model': self.model, 'name': values.view_name, 'priority': values.priority, 'type': values.view_type, 'arch': arch}; self.dataset.create(vals, function(suc) { def.resolve(); @@ -157,25 +153,29 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ }); return def.promise(); }, - on_valid_create_view: function() { + on_valid_create_view: function(widgets) { var msg = "
    "; - _.each(self.create_view_widget, function(widget) { - if (widget.invalid) { + _.each(widgets, function(widget) { + if (widget.is_invalid) { msg += "
  • " + widget.name + "
  • "; } }); msg += "
"; - self.do_warn("The following fields are invalid :", msg); + this.do_warn("The following fields are invalid :", msg); }, add_node_name : function(node) { if(node.tagName.toLowerCase() == "button" || node.tagName.toLowerCase() == "field"){ return (node.getAttribute('name'))? - _.sprintf( "<%s name='%s'>",node.tagName.toLowerCase(), node.getAttribute('name')): - _.sprintf( "<%s>",node.tagName.toLowerCase()); + _.str.sprintf( "<%s name='%s'>",node.tagName.toLowerCase(), node.getAttribute('name')): + _.str.sprintf( "<%s>",node.tagName.toLowerCase()); + }else if(node.tagName.toLowerCase() == "group"){ + return (node.getAttribute('string'))? + _.str.sprintf( "<%s>",node.getAttribute('string')): + _.str.sprintf( "<%s>",node.tagName.toLowerCase()); }else{ return (node.getAttribute('string'))? - _.sprintf( "<%s string='%s'>",node.tagName.toLowerCase(), node.getAttribute('string')): - _.sprintf( "<%s>",node.tagName.toLowerCase()); + _.str.sprintf( "<%s string='%s'>",node.tagName.toLowerCase(), node.getAttribute('string')): + _.str.sprintf( "<%s>",node.tagName.toLowerCase()); } }, do_delete_view: function() { @@ -237,7 +237,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ 'level': 0, 'id': this.xml_element_id +=1, 'att_list': [], - 'name': _.sprintf("", view_id), + 'name': _.str.sprintf("", view_id), 'child_id': [] }; var xml_arch = QWeb.load_xml(arch); @@ -249,7 +249,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ this.dataset.read_ids([parseInt(self.main_view_id)], ['arch', 'type'], function(arch) { if (arch.length) { var arch_object = self.parse_xml(arch[0].arch, self.main_view_id); - self.main_view_type = arch[0].type + self.main_view_type = arch[0].type == 'tree'? 'list': arch[0].type; view_arch_list.push({"view_id": self.main_view_id, "arch": arch[0].arch}); self.dataset.read_slice([], {domain: [['inherit_id','=', parseInt(self.main_view_id)]]}, function(result) { _.each(result, function(res) { @@ -295,7 +295,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ }); }else{ var temp = _.reject(xpath_arch_object[0].child_id[0].att_list, function(list) { - return _.include(list, "position") + return list instanceof Array? _.include(list, "position"): false }); expr_to_list = [_.flatten(temp)]; } @@ -382,12 +382,12 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ this.one_object = one_object; this.edit_xml_dialog = new openerp.web.Dialog(this, { modal: true, - title: _.sprintf("View Editor %d - %s", self.main_view_id, self.model), + title: _.str.sprintf("View Editor %d - %s", self.main_view_id, self.model), width: 750, height: 500, buttons: { "Inherited View": function() { - //todo + //TODO }, "Preview": function() { var action = { @@ -439,7 +439,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ var view_id,view_xml_id; var view_find = side; - //for view id found + //for view id finding var min_level = this.one_object.clicked_tr_id; if(($(side).find('a').text()).search("view_id") != -1){ view_id = parseInt(($(view_find).find('a').text()).replace(/[^0-9]+/g, '')); @@ -494,7 +494,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ _.each([tr,parent_tr],function(element){ property_to_check.push( _.detect(_.keys(_CHILDREN),function(res){ - return _.includes(element, res); + return _.str.include(element, res); })); }); self.on_add_node(property_to_check, fields); @@ -502,12 +502,17 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ }, do_node_edit: function(side){ var self = this; - var tr = $(side).find('a').text(); + var result = self.get_object_by_id(this.one_object.clicked_tr_id,this.one_object['main_object'], []); + if (result.length && result[0] && result[0].att_list) { + var properties = _PROPERTIES[result[0].att_list[0]]; + self.on_edit_node(properties); + } +/* var tr = $(side).find('a').text(); var tag = _.detect(_.keys(_PROPERTIES),function(res){ return _.includes(tr, res); }); var properties = _PROPERTIES[tag]; - self.on_edit_node(properties); + self.on_edit_node(properties);*/ }, do_node_down: function(cur_tr, img){ var self = this; @@ -663,18 +668,15 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ parent = $(arch1).parents(); } else if (move_direct == "update_node") { - _.each(update_values, function(val){ - if(val[0] == "required"){ - $(arch1).attr("required", "true"); - }else{ - $(arch1).attr(val[0],val[1]); - } + if (val[1]) $(arch1)[0].setAttribute(val[0], val[1]); + else $(arch1)[0].removeAttribute(val[0]); }); var new_obj = self.create_View_Node(arch1); new_obj.id = obj.id,new_obj.child_id = obj.child_id; - self.edit_xml_dialog.$element.find("tr[id='viewedit-"+self.one_object.clicked_tr_id+"']") - .find('a').text(new_obj.name); + self.edit_xml_dialog.$element. + find("tr[id='viewedit-"+this.one_object.clicked_tr_id+"']"). + find('a').text(new_obj.name); child_list.splice(index, 1, new_obj); parent = $(arch1).parents(); @@ -802,14 +804,23 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ height: 400, buttons: { "Update": function(){ + var warn = false; var update_values = []; _.each(self.edit_widget, function(widget) { - if (widget.dirty && !widget.invalid) { + if (widget.is_invalid) { + warn = true; + return false; + }; + if (widget.dirty && !widget.is_invalid) { update_values.push([widget.name, widget.get_value()]); } }); - self.do_save_update_arch("update_node", update_values); - self.edit_node_dialog.close(); + if (warn) { + self.on_valid_create_view(self.edit_widget); + } else { + self.do_save_update_arch("update_node", update_values); + self.edit_node_dialog.close(); + } }, "Cancel": function(){ self.edit_node_dialog.close(); @@ -817,24 +828,63 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ } }); this.edit_node_dialog.start().open(); - var widget = _.keys(self.property.map); + var _PROPERTIES_ATTRIBUTES = { + 'name' : {'name':'name', 'string': 'Name', 'type': 'char'}, + 'string' : {'name':'string', 'string': 'String', 'type': 'char'}, + 'required' : {'name':'required', 'string': 'Required', 'type': 'boolean'}, + 'readonly' : {'name':'readonly', 'string': 'Readonly', 'type': 'boolean'}, + 'domain' : {'name':'domain', 'string': 'Domain', 'type': 'char'}, + 'context' : {'name':'context', 'string': 'Context', 'type': 'char'}, + 'limit' : {'name':'limit', 'string': 'Limit', 'type': 'float'}, + 'min_rows' : {'name':'min_rows', 'string': 'Minimum rows', 'type': 'float'}, + 'date_start' : {'name':'date_start', 'string': 'Start date', 'type': 'char'}, + 'date_delay' : {'name':'date_delay', 'string': 'Delay date', 'type': 'char'}, + 'day_length' : {'name':'day_length', 'string': 'Day length', 'type': 'char'}, + 'mode' : {'name':'mode', 'string': 'Mode', 'type': 'char'}, + 'align' : {'name':'align', 'string': 'Alignment ', 'type': 'selection', 'selection': [['', ''], ['0.0', 'Left'], ['0.5', 'Center'], ['1.0', 'Right']]}, + 'icon' : {'name':'icon', 'string': 'Icon', 'type': 'selection', 'selection': _ICONS}, + 'type' : {'name':'type', 'string': 'Type', 'type': 'selection', 'selection': [['', ''], ['action', 'Action'], ['object', 'Object'], ['workflow', 'Workflow'], ['server_action', 'Server Action']]}, + 'special' : {'name':'special', 'string': 'Special', 'type': 'selection', 'selection': [['',''],['save', 'Save Button'], ['cancel', 'Cancel Button'], ['open', 'Open Button']]}, + 'target' : {'name':'target', 'string': 'Target', 'type': 'selection', 'selection': [['', ''], ['new', 'New Window']]}, + 'confirm' : {'name':'confirm', 'string': 'Confirm', 'type': 'char'}, + 'style' : {'name':'style', 'string': 'Style', 'type': 'selection', 'selection':[["",""],["1", "1"],["1-1", "1-1"],["1-2", "1-2"],["2-1", "2-1"],["1-1-1", "1-1-1"]]}, + 'filename' : {'name':'filename', 'string': 'File Name', 'type': 'char'}, + 'width' : {'name':'width', 'string': 'Width', 'type': 'float'}, + 'height' : {'name':'height', 'string': 'Height', 'type': 'float'}, + 'attrs' : {'name':'attrs', 'string': 'Attrs', 'type': 'char'}, + 'col' : {'name':'col', 'string': 'col', 'type': 'float'}, + 'link' : {'name':'link', 'string': 'Link', 'type': 'char'}, + 'position' : {'name':'position', 'string': 'Position', 'type': 'selection', 'selection': [['',''],['after', 'After'],['before', 'Before'],['inside', 'Inside'],['replace', 'Replace']]}, + 'states' : {'name':'states', 'string': 'states', 'type': 'char'}, + 'eval' : {'name':'eval', 'string': 'Eval', 'type': 'char'}, + 'ref' : {'name':'ref', 'string': 'Ref', 'type': 'char'}, + 'on_change' : {'name':'on_change', 'string': 'On change', 'type': 'char'}, + 'nolabel' : {'name':'nolabel', 'string': 'No label', 'type': 'boolean'}, + 'completion' : {'name':'completion', 'string': 'Completion', 'type': 'boolean'}, + 'colspan' : {'name':'colspan', 'string': 'Colspan', 'type': 'float'}, + 'widget' : {'name':'widget', 'string': 'widget', 'type': 'selection'}, + 'colors' : {'name':'colors', 'string': 'Colors', 'type': 'char'}, + 'editable' : {'name':'editable', 'string': 'Editable', 'type': 'selection', 'selection': [["",""],["top","Top"],["bottom", "Bottom"]]}, + 'groups' : {'name':'groups', 'string': 'Groups', 'type': 'seleciton_multi'}, + }; var arch_val = self.get_object_by_id(this.one_object.clicked_tr_id,this.one_object['main_object'], []); this.edit_node_dialog.$element.append('
'); this.edit_widget = []; - _.each(properties, function(property) { - type_widget = false; - self.ready = $.when(self.on_groups(property)).then(function () { - if (_.include(widget, property)){ - type_widget = new (self.property.get_any([property])) (self.edit_node_dialog, property); - } else { - type_widget = new openerp.web.ViewEditor.FieldChar (self.edit_node_dialog, property); - } + self.ready = $.when(self.on_groups(properties)).then(function () { + _PROPERTIES_ATTRIBUTES['groups']['selection'] = self.groups; + var values = _.keys( openerp.web.form.widgets.map); + values.push(''); + values.sort(); + _PROPERTIES_ATTRIBUTES['widget']['selection'] = values; + var widgets = _.filter(_PROPERTIES_ATTRIBUTES, function(property){ return _.include(properties, property.name)}) + _.each(widgets, function(widget) { + var type_widget = new (self.property.get_any([widget.type])) (self.edit_node_dialog, widget); var value = _.detect(arch_val[0]['att_list'],function(res) { - return _.include(res, property); + if (res instanceof Array) return _.include(res, widget.name); + else return false; }); value = value instanceof Array ? value[1] : value; - if (property == 'groups') type_widget.selection = self.groups; - self.edit_node_dialog.$element.find('table[id=rec_table]').append('' + property + ':' + type_widget.render() + ''); + self.edit_node_dialog.$element.find('table[id=rec_table]').append('' + widget.string + ':' + type_widget.render() + ''); type_widget.start(); type_widget.set_value(value); self.edit_widget.push(type_widget); @@ -842,12 +892,12 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ }); }, //for getting groups - on_groups: function(property){ + on_groups: function(properties){ var self = this, def = $.Deferred(); - if (property != 'groups') { + if (!_.include(properties, 'groups')) { self.groups = false; - return false; + def.resolve(); } var group_ids = [], group_names = {}, @@ -862,7 +912,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ group_names[key]=res.name; group_ids.push(res.id); }); - model_data + model_data .read_slice([],{domain:[['res_id', 'in', group_ids],['model','=','res.groups']]}) .done(function(model_grp) { _.each(model_grp,function(res_group){ @@ -879,9 +929,9 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ var positions = ['After','Before','Inside']; var render_list = []; var render_list =[{'name': 'node_type','selection': _.keys(_CHILDREN).sort(), - 'value': 'field', 'string': 'Node Type'}, - {'name': 'position','selection': positions, 'value': false, 'string': 'Position'}, - {'name': 'field_value','selection': fields, 'value': false, 'string': ''}]; + 'value': 'field', 'string': 'Node Type','type': 'selection'}, + {'name': 'position','selection': positions, 'value': false, 'string': 'Position','type': 'selection'}, + {'name': 'field_value','selection': fields, 'value': false, 'string': '','type': 'selection'}]; this.add_widget = []; this.add_node_dialog = new openerp.web.Dialog(this,{ modal: true, @@ -903,8 +953,8 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ } if(check_add_node){ var tag = (values.node_type == "field")? - _.sprintf("<%s name='%s'> ",values.node_type,values.field_value,values.node_type): - _.sprintf("<%s> ",values.node_type,values.node_type); + _.str.sprintf("<%s name='%s'> ",values.node_type,values.field_value,values.node_type): + _.str.sprintf("<%s> ",values.node_type,values.node_type); self.do_save_update_arch("add_node", [tag, values.position]); }else{alert("Can't Update View");} }, @@ -917,8 +967,7 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ append('
'); var table_selector = self.add_node_dialog.$element.find('table[id=rec_table]'); _.each(render_list,function(node){ - type_widget = new openerp.web.ViewEditor.FieldSelect (self.add_node_dialog, node.name); - type_widget.selection = node.selection; + type_widget = new (self.property.get_any([node.type])) (self.add_node_dialog, node); table_selector.append('' + node.string + ':' + type_widget.render() + ''); type_widget.start(); type_widget.set_value(node.value); @@ -963,33 +1012,39 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ } }); openerp.web.ViewEditor.Field = openerp.web.Class.extend({ - init: function(view, name) { + init: function(view, widget) { this.$element = view.$element; this.dirty = false; - this.name = name; - this.required = false; - this.invalid = false; + this.name = widget.name; + this.selection = widget.selection || []; + this.required = widget.required || false; + this.string = widget.string || ""; + this.type = widget.type; + this.is_invalid = false; }, start: function () { this.update_dom(); }, update_dom: function() { - this.$element.find("td[id="+ this.name+"]").toggleClass('invalid', this.invalid); + this.$element.find("td[id="+ this.name+"]").toggleClass('invalid', this.is_invalid); this.$element.find("td[id="+ this.name+"]").toggleClass('required', this.required); }, on_ui_change: function() { - var value = this.get_value(); - value = value instanceof Array ? value[1] : value; - if (this.required && !value) { - this.invalid = true; - } else { - this.invalid = false; - } + this.validate(); this.dirty = true; this.update_dom(); }, + validate: function() { + this.is_invalid = false; + try { + var value = openerp.web.parse_value(this.get_value(), this, ''); + this.is_invalid = this.required && value === ''; + } catch(e) { + this.is_invalid = true; + } + }, render: function() { - return _.sprintf("%s", this.name, QWeb.render(this.template, {widget: this})) + return _.str.sprintf("%s", this.name, QWeb.render(this.template, {widget: this})) }, }); openerp.web.ViewEditor.FieldBoolean = openerp.web.ViewEditor.Field.extend({ @@ -1000,7 +1055,6 @@ openerp.web.ViewEditor.FieldBoolean = openerp.web.ViewEditor.Field.extend({ this.$element.find("input[id="+ self.name+"]").change(function() { self.on_ui_change(); }); - }, set_value: function(value) { if (value) { @@ -1008,7 +1062,8 @@ openerp.web.ViewEditor.FieldBoolean = openerp.web.ViewEditor.Field.extend({ } }, get_value: function() { - return this.$element.find("input[id=" + this.name + "]").is(':checked') || null; + var value = this.$element.find("input[id=" + this.name + "]").is(':checked') + return value? "1" : null; } }); openerp.web.ViewEditor.FieldChar = openerp.web.ViewEditor.Field.extend({ @@ -1029,10 +1084,6 @@ openerp.web.ViewEditor.FieldChar = openerp.web.ViewEditor.Field.extend({ }); openerp.web.ViewEditor.FieldSelect = openerp.web.ViewEditor.Field.extend({ template : "vieweditor_selection", - init: function(view, name) { - this._super(view, name); - this.selection = false; - }, start: function () { var self = this; this._super(); @@ -1058,53 +1109,7 @@ openerp.web.ViewEditor.FieldSelect = openerp.web.ViewEditor.Field.extend({ return this.$element.find("select[id=" + this.name + "]").val(); } }); -openerp.web.ViewEditor.WidgetProperty = openerp.web.ViewEditor.FieldSelect.extend({ - init: function(view, name) { - this._super(view, name); - this.registry = openerp.web.form.widgets; - var values = _.keys(this.registry.map); - values.push(''); - values.sort(); - this.selection = values; - }, -}); -openerp.web.ViewEditor.IconProperty = openerp.web.ViewEditor.FieldSelect.extend({ - init: function(view, name) { - this._super(view, name); - this.selection = _ICONS; - }, -}); -openerp.web.ViewEditor.ButtonTargetProperty = openerp.web.ViewEditor.FieldSelect.extend({ - init: function(view, name) { - this._super(view, name); - this.selection = [['', ''], ['new', 'New Window']]; - }, -}); -openerp.web.ViewEditor.ButtonTypeProperty = openerp.web.ViewEditor.FieldSelect.extend({ - init: function(view, name) { - this._super(view, name); - this.selection = [['', ''], ['action', 'Action'], ['object', 'Object'], ['workflow', 'Workflow'], ['server_action', 'Server Action']]; - }, -}); -openerp.web.ViewEditor.AlignProperty = openerp.web.ViewEditor.FieldSelect.extend({ - init: function(view, name) { - this._super(view, name); - this.selection = [['', ''], ['0.0', 'Left'], ['0.5', 'Center'], ['1.0', 'Right']]; - }, -}); -openerp.web.ViewEditor.ButtonSpecialProperty = openerp.web.ViewEditor.FieldSelect.extend({ - init: function(view, name) { - this._super(view, name); - this.selection = [['',''],['save', 'Save Button'], ['cancel', 'Cancel Button'], ['open', 'Open Button']]; - }, -}); -openerp.web.ViewEditor.PositionProperty = openerp.web.ViewEditor.FieldSelect.extend({ - init: function(view, name) { - this._super(view, name); - this.selection = [['',''],['after', 'After'],['before', 'Before'],['inside', 'Inside'],['replace', 'Replace']]; - }, -}); -openerp.web.ViewEditor.GroupsProperty = openerp.web.ViewEditor.FieldSelect.extend({ +openerp.web.ViewEditor.FieldSelectMulti = openerp.web.ViewEditor.FieldSelect.extend({ start: function () { this._super(); this.$element.find("select[id=" + this.name + "]").css('height', '100px').attr("multiple",true); @@ -1120,9 +1125,13 @@ openerp.web.ViewEditor.GroupsProperty = openerp.web.ViewEditor.FieldSelect.exten }); } }); +openerp.web.ViewEditor.FieldFloat = openerp.web.ViewEditor.FieldChar.extend({ + +}); + var _PROPERTIES = { 'field' : ['name', 'string', 'required', 'readonly', 'domain', 'context', 'nolabel', 'completion', - 'colspan', 'widget', 'eval', 'ref', 'on_change', 'groups', 'attrs'], + 'colspan', 'widget', 'eval', 'ref', 'on_change', 'attrs', 'groups'], 'form' : ['string', 'col', 'link'], 'notebook' : ['colspan', 'position', 'groups'], 'page' : ['string', 'states', 'attrs', 'groups'], @@ -1130,7 +1139,7 @@ var _PROPERTIES = { 'image' : ['filename', 'width', 'height', 'groups'], 'separator' : ['string', 'colspan', 'groups'], 'label': ['string', 'align', 'colspan', 'groups'], - 'button': ['name', 'string', 'icon', 'type', 'states', 'readonly', 'special', 'target', 'confirm', 'context', 'attrs', 'groups','colspan'], + 'button': ['name', 'string', 'icon', 'type', 'states', 'readonly', 'special', 'target', 'confirm', 'context', 'attrs', 'colspan', 'groups'], 'newline' : [], 'board': ['style'], 'column' : [], @@ -1156,7 +1165,7 @@ var _CHILDREN = { 'newline': [], 'separator': [], }; -var icons = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', +var _ICONS = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER', 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE', 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO', @@ -1184,19 +1193,10 @@ var icons = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph' ]; openerp.web.ViewEditor.property_widget = new openerp.web.Registry({ - 'required' : 'openerp.web.ViewEditor.FieldBoolean', - 'readonly' : 'openerp.web.ViewEditor.FieldBoolean', - 'nolabel' : 'openerp.web.ViewEditor.FieldBoolean', - 'completion' : 'openerp.web.ViewEditor.FieldBoolean', - 'widget' : 'openerp.web.ViewEditor.WidgetProperty', - 'groups' : 'openerp.web.ViewEditor.GroupsProperty', - 'position' : 'openerp.web.ViewEditor.PositionProperty', - 'icon' : 'openerp.web.ViewEditor.IconProperty', - 'align' : 'openerp.web.ViewEditor.AlignProperty', - 'special' : 'openerp.web.ViewEditor.ButtonSpecialProperty', - 'type' : 'openerp.web.ViewEditor.ButtonTypeProperty', - 'target' : 'openerp.web.ViewEditor.ButtonTargetProperty', + 'boolean' : 'openerp.web.ViewEditor.FieldBoolean', + 'seleciton_multi' : 'openerp.web.ViewEditor.FieldSelectMulti', 'selection' : 'openerp.web.ViewEditor.FieldSelect', 'char' : 'openerp.web.ViewEditor.FieldChar', + 'float' : 'openerp.web.ViewEditor.FieldFloat', }); }; diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 84041ef8b60..d833b7471c6 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -35,7 +35,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.fields = {}; this.datarecord = {}; this.show_invalid = true; - this.dirty_for_user = false; this.default_focus_field = null; this.default_focus_button = null; this.registry = this.readonly ? openerp.web.form.readonly : openerp.web.form.widgets; @@ -141,7 +140,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# // null index means we should start a new record promise = this.on_button_new(); } else { - promise = this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); + promise = this.dataset.read_index(_.keys(this.fields_view.fields)).pipe(this.on_record_loaded); } this.$element.show(); if (this.sidebar) { @@ -156,6 +155,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } }, on_record_loaded: function(record) { + var self = this, + deferred_stack = $.Deferred.queue(); if (!record) { throw("Form: No record received"); } @@ -172,35 +173,42 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } this.$form_header.find('.oe_form_on_readonly').toggle(this.readonly); this.$form_header.find('.oe_form_on_editable').toggle(!this.readonly); - this.dirty_for_user = false; this.datarecord = record; - for (var f in this.fields) { - var field = this.fields[f]; + + _(this.fields).each(function (field, f) { 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.show_invalid = false; - for (var f in record) { - var field = this.fields[f]; - if (field) { - field.dirty = true; - this.do_onchange(field); + var result = field.set_value(self.datarecord[f] || false); + if (result && _.isFunction(result.promise)) { + deferred_stack.push(result); + } + $.when(result).then(function() { + field.validate(); + }); + }); + deferred_stack.push('force resolution if no fields'); + return deferred_stack.then(function() { + if (!record.id) { + // New record: Second pass in order to trigger the onchanges + self.show_invalid = false; + for (var f in record) { + var field = self.fields[f]; + if (field) { + field.dirty = true; + self.do_onchange(field); + } } } - } - this.on_form_changed(); - this.initial_mutating_lock.resolve(); - this.show_invalid = true; - this.do_update_pager(record.id == null); - if (this.sidebar) { - this.sidebar.attachments.do_update(); - } - if (this.default_focus_field && !this.embedded_view) { - this.default_focus_field.focus(); - } + self.on_form_changed(); + self.initial_mutating_lock.resolve(); + self.show_invalid = true; + self.do_update_pager(record.id == null); + if (self.sidebar) { + self.sidebar.attachments.do_update(); + } + if (self.default_focus_field && !self.embedded_view) { + self.default_focus_field.focus(); + } + }); }, on_form_changed: function() { for (var w in this.widgets) { @@ -240,7 +248,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# try { processed = processed || []; if (widget.node.attrs.on_change) { - var onchange = _.trim(widget.node.attrs.on_change); + var onchange = _.str.trim(widget.node.attrs.on_change); var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/); if (call) { var method = call[1], args = []; @@ -257,7 +265,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }; var parent_fields = null; _.each(call[2].split(','), function(a, i) { - var field = _.trim(a); + var field = _.str.trim(a); if (field in argument_replacement) { args.push(argument_replacement[field](i)); return; @@ -267,11 +275,11 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# return; } else { var splitted = field.split('.'); - if (splitted.length > 1 && _.trim(splitted[0]) === "parent" && self.dataset.parent_view) { + if (splitted.length > 1 && _.str.trim(splitted[0]) === "parent" && self.dataset.parent_view) { if (parent_fields === null) { parent_fields = self.dataset.parent_view.get_fields_values(); } - var p_val = parent_fields[_.trim(splitted[1])]; + var p_val = parent_fields[_.str.trim(splitted[1])]; if (p_val !== undefined) { args.push(p_val == null ? false : p_val); return; @@ -317,7 +325,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# processed.push(field.name); if (field.get_value() != value) { field.set_value(value); - field.dirty = this.dirty_for_user = true; + field.dirty = true; if (_.indexOf(processed, field.name) < 0) { this.do_onchange(field, processed); } @@ -356,12 +364,13 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var keys = _.keys(self.fields_view.fields); $.when(self.do_set_editable()).then(function() { if (keys.length) { - self.dataset.default_get(keys).then(self.on_record_loaded).then(function() { + self.dataset.default_get(keys).pipe(self.on_record_loaded).then(function() { def.resolve(); }); } else { - self.on_record_loaded({}); - def.resolve(); + self.on_record_loaded({}).then(function() { + def.resolve(); + }); } }); } @@ -402,7 +411,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# return def.promise(); }, can_be_discarded: function() { - return !this.dirty_for_user || confirm(_t("Warning, the record has been modified, your changes will be discarded.")); + return !this.is_dirty() || confirm(_t("Warning, the record has been modified, your changes will be discarded.")); }, /** * Triggers saving the form's record. Chooses between creating a new @@ -418,8 +427,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# try { if (!self.initial_mutating_lock.isResolved() && !self.initial_mutating_lock.isRejected()) return; - var form_dirty = false, - form_invalid = false, + var form_invalid = false, values = {}, first_invalid_field = null; for (var f in self.fields) { @@ -431,7 +439,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# first_invalid_field = f; } } else if (f.is_dirty()) { - form_dirty = true; values[f.name] = f.get_value(); } } @@ -480,8 +487,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# // should not happen in the server, but may happen for internal purpose return $.Deferred().reject(); } else { - this.reload(); - return $.when(r).then(success); + return $.when(this.reload()).pipe(function () { + return $.when(r).then(success); }, null); } }, /** @@ -528,7 +535,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# if (self.dataset.index == null || self.dataset.index < 0) { return $.when(self.on_button_new()); } else { - return self.dataset.read_index(_.keys(self.fields_view.fields), self.on_record_loaded); + return self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_record_loaded); } }; this.reload_lock = this.reload_lock.pipe(act, act); @@ -553,6 +560,11 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# return self.dataset.parent_view.recursive_save(); }); }, + is_dirty: function() { + return _.any(this.fields, function (value) { + return value.is_dirty(); + }); + }, is_interactible_record: function() { var id = this.datarecord.id; if (!id) { @@ -638,7 +650,7 @@ openerp.web.form.SidebarAttachments = openerp.web.Widget.extend({ }, on_attachment_delete: function(e) { var self = this, $e = $(e.currentTarget); - var name = _.trim($e.parent().find('a.oe-sidebar-attachments-link').text()); + var name = _.str.trim($e.parent().find('a.oe-sidebar-attachments-link').text()); if (confirm("Do you really want to delete the attachment " + name + " ?")) { this.rpc('/web/dataset/unlink', { model: 'ir.attachment', @@ -798,8 +810,13 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form. _build_view_fields_values: function() { var a_dataset = this.view.dataset; var fields_values = this.view.get_fields_values(); - var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {}; - fields_values.parent = parent_values; + var active_id = a_dataset.ids[a_dataset.index]; + _.extend(fields_values, { + active_id: active_id || false, + active_ids: active_id ? [active_id] : [], + active_model: a_dataset.model, + parent: a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {} + }); return fields_values; }, _build_eval_context: function() { @@ -1231,7 +1248,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f } }, on_ui_change: function() { - this.dirty = this.view.dirty_for_user = true; + this.dirty = true; this.validate(); if (this.is_valid()) { this.set_value_from_ui(); @@ -1344,54 +1361,58 @@ openerp.web.DateTimeWidget = openerp.web.Widget.extend({ template: "web.datetimepicker", jqueryui_object: 'datetimepicker', type_of_date: "datetime", + init: function(parent) { + this._super(parent); + this.name = parent.name; + }, start: function() { var self = this; - this.$element.find('input').change(this.on_change); + this.$input = this.$element.find('input.oe_datepicker_master'); + this.$input_picker = this.$element.find('input.oe_datepicker_container'); + this.$input.change(this.on_change); this.picker({ onSelect: this.on_picker_select, changeMonth: true, changeYear: true, showWeek: true, - showButtonPanel: false + showButtonPanel: true }); this.$element.find('img.oe_datepicker_trigger').click(function() { if (!self.readonly) { self.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date()); - self.$element.find('.oe_datepicker').toggle(); + self.$input_picker.show(); + self.picker('show'); + self.$input_picker.hide(); } }); - this.$element.find('.ui-datepicker-inline').removeClass('ui-widget-content ui-corner-all'); - this.$element.find('button.oe_datepicker_close').click(function() { - self.$element.find('.oe_datepicker').hide(); - }); this.set_readonly(false); this.value = false; }, picker: function() { - return $.fn[this.jqueryui_object].apply(this.$element.find('.oe_datepicker_container'), arguments); + return $.fn[this.jqueryui_object].apply(this.$input_picker, arguments); }, on_picker_select: function(text, instance) { var date = this.picker('getDate'); - this.$element.find('input').val(date ? this.format_client(date) : '').change(); + this.$input.val(date ? this.format_client(date) : '').change(); }, set_value: function(value) { this.value = value; - this.$element.find('input').val(value ? this.format_client(value) : ''); + this.$input.val(value ? this.format_client(value) : ''); }, get_value: function() { return this.value; }, set_value_from_ui: function() { - var value = this.$element.find('input').val() || false; + var value = this.$input.val() || false; this.value = this.parse_client(value); }, set_readonly: function(readonly) { this.readonly = readonly; - this.$element.find('input').attr('disabled', this.readonly); + this.$input.attr('disabled', this.readonly); this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly); }, is_valid: function(required) { - var value = this.$element.find('input').val(); + var value = this.$input.val(); if (value === "") { return !required; } else { @@ -1404,7 +1425,7 @@ openerp.web.DateTimeWidget = openerp.web.Widget.extend({ } }, focus: function() { - this.$element.find('input').focus(); + this.$input.focus(); }, parse_client: function(v) { return openerp.web.parse_value(v, {"widget": this.type_of_date}); @@ -1421,11 +1442,7 @@ openerp.web.DateTimeWidget = openerp.web.Widget.extend({ openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({ jqueryui_object: 'datepicker', - type_of_date: "date", - on_picker_select: function(text, instance) { - this._super(text, instance); - this.$element.find('.oe_datepicker').hide(); - } + type_of_date: "date" }); openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({ @@ -1437,7 +1454,7 @@ openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({ var self = this; this._super.apply(this, arguments); this.datewidget = this.build_widget(); - this.datewidget.on_change.add(this.on_ui_change); + this.datewidget.on_change.add_last(this.on_ui_change); this.datewidget.appendTo(this.$element); }, set_value: function(value) { @@ -1825,7 +1842,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ if (search_val.length > 0 && !_.include(raw_result, search_val) && (!self.value || search_val !== self.value[1])) { - values.push({label: _.sprintf(_t('   Create "%s"'), + values.push({label: _.str.sprintf(_t('   Create "%s"'), $('').text(search_val).html()), action: function() { self._quick_create(search_val); }}); @@ -2103,7 +2120,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ }, reload_current_view: function() { var self = this; - self.is_loaded = self.is_loaded.pipe(function() { + return self.is_loaded = self.is_loaded.pipe(function() { var view = self.viewmanager.views[self.viewmanager.active_view].controller; if(self.viewmanager.active_view === "list") { return view.reload_content(); @@ -2176,8 +2193,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ if (this.dataset.index === null && this.dataset.ids.length > 0) { this.dataset.index = 0; } - self.reload_current_view(); - this.is_setted.resolve(); + self.is_setted.resolve(); + return self.reload_current_view(); }, get_value: function() { var self = this; @@ -2955,7 +2972,7 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ render_list: function() { var self = this; var shown = _.map(((this.node.attrs || {}).statusbar_visible || "").split(","), - function(x) { return _.trim(x); }); + function(x) { return _.str.trim(x); }); shown = _.select(shown, function(x) { return x.length > 0; }); if (shown.length == 0) { diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 77f04d4c864..728b090d679 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -295,7 +295,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# } else { last = first + limit; } - this.$element.find('span.oe-pager-state').empty().text(_.sprintf( + this.$element.find('span.oe-pager-state').empty().text(_.str.sprintf( "[%d to %d] of %d", first + 1, last, total)); this.$element @@ -647,7 +647,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# return; } - $footer_cells.filter(_.sprintf('[data-field=%s]', column.id)) + $footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id)) .html(openerp.web.format_cell(aggregation, column, undefined, false)); }); }, @@ -1127,7 +1127,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L child.datagroup = group; var $row = child.$row = $(''); - if (group.openable) { + if (group.openable && group.length) { $row.click(function (e) { if (!$row.data('open')) { $row.data('open', true) @@ -1159,7 +1159,13 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L } catch (e) { $group_column.html(row_data[group_column.id].value); } - if (group.openable) { + if (!group.length) { + // Kinda-ugly hack: jquery-ui has no "empty" icon, so set + // wonky background position to ensure nothing is displayed + // there but the rest of the behavior is ui-icon's + $group_column.prepend( + ''); + } else if (group.openable) { // Make openable if not terminal group & group_by_no_leaf $group_column .prepend(''); @@ -1186,7 +1192,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L format = "%.2f"; } $('') - .text(_.sprintf(format, value)) + .text(_.str.sprintf(format, value)) .appendTo($row); } else { $row.append(''); @@ -1245,7 +1251,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L var pages = Math.ceil(dataset.ids.length / limit); self.$row .find('.oe-pager-state') - .text(_.sprintf('%d/%d', page + 1, pages)) + .text(_.str.sprintf('%d/%d', page + 1, pages)) .end() .find('button[data-pager-action=previous]') .attr('disabled', page === 0) diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js index 5143adb3838..34d47b92813 100644 --- a/addons/web/static/src/js/view_tree.js +++ b/addons/web/static/src/js/view_tree.js @@ -42,7 +42,8 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# model: this.model, view_id: this.view_id, view_type: "tree", - toolbar: this.view_manager ? !!this.view_manager.sidebar : false + toolbar: this.view_manager ? !!this.view_manager.sidebar : false, + context: this.dataset.get_context() }, this.on_loaded); }, /** @@ -214,17 +215,23 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# // Get details in listview activate: function(id) { var self = this; + var local_context = { + active_model: self.dataset.model, + active_id: id, + active_ids: [id]}; this.rpc('/web/treeview/action', { id: id, model: this.dataset.model, context: new openerp.web.CompoundContext( - this.dataset.get_context(), { - active_model: this.dataset.model, - active_id: id, - active_ids: [id]}) + this.dataset.get_context(), local_context) }, function (actions) { if (!actions.length) { return; } var action = actions[0][2]; + var c = new openerp.web.CompoundContext(local_context); + if (action.context) { + c.add(action.context); + } + action.context = c; self.do_action(action); }); }, diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index efa5ac5b2a8..eff94b95ad7 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -210,7 +210,7 @@ session.web.ViewManager = session.web.Widget.extend(/** @lends session.web.View sidebar_id : self.element_id + '_sidebar_' + view.view_type, action : self.action, action_views_ids : views_ids - }, self.flags, view.options || {}) + }, self.flags, self.flags[view.view_type] || {}, view.options || {}) }); views_ids[view.view_type] = view.view_id; }); @@ -534,7 +534,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner $logs_list = $logs.find('ul').empty(); $logs.toggleClass('oe-has-more', log_records.length > cutoff); _(log_records.reverse()).each(function (record) { - $(_.sprintf('
  • %s
  • ', record.name)) + $(_.str.sprintf('
  • %s
  • ', record.name)) .appendTo($logs_list) .delegate('a', 'click', function (e) { self.do_action({ @@ -628,7 +628,7 @@ session.web.Sidebar = session.web.Widget.extend({ }, add_section: function(name, code) { - if(!code) code = _.underscored(name); + if(!code) code = _.str.underscored(name); var $section = this.sections[code]; if(!$section) { @@ -656,7 +656,7 @@ session.web.Sidebar = session.web.Widget.extend({ // var self = this, - $section = this.add_section(_.titleize(section_code.replace('_', ' ')), section_code), + $section = this.add_section(_.str.titleize(section_code.replace('_', ' ')), section_code), section_id = $section.attr('id'); if (items) { diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 25cfaee1232..6d35cf15d6c 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -18,7 +18,7 @@ -
    +
    @@ -63,7 +63,9 @@
    +
    Loading... +
      @@ -703,7 +705,7 @@ Unhandled widget - + console.warn('Unhandled widget', dict.widget); @@ -896,13 +898,13 @@
      - + - @@ -1070,7 +1072,7 @@
      - @@ -1152,8 +1154,8 @@ t-att-title="attrs.help" t-att-class="classes.join(' ')" t-att-autofocus="attrs.default_focus === '1' ? 'autofocus' : undefined"> - -
      + +
      Welcome to your new OpenERP instance.
    • Remember to bookmark this page.
    • -
    • Remember your login:
    • +
    • Remember your login:
    • Choose the first OpenERP Application you want to install..
    • diff --git a/addons/web_graph/static/src/js/graph.js b/addons/web_graph/static/src/js/graph.js index e183fc5cc43..f2af5503381 100644 --- a/addons/web_graph/static/src/js/graph.js +++ b/addons/web_graph/static/src/js/graph.js @@ -233,7 +233,7 @@ openerp.web_graph.GraphView = openerp.web.View.extend({ // second argument is coerced to a str, no good for boolean r[self.abscissa] = records[0][self.abscissa]; _(records).each(function (record) { - var key = _.sprintf('%s_%s', + var key = _.str.sprintf('%s_%s', self.ordinate, record[self.group_field].toLowerCase().replace(/\s/g, '_')); r[key] = record[self.ordinate]; @@ -278,7 +278,7 @@ openerp.web_graph.GraphView = openerp.web.View.extend({ border: false, width: 1024, tooltip:{ - template: _.sprintf("#%s#, %s=#%s#", + template: _.str.sprintf("#%s#, %s=#%s#", self.abscissa, group_list[0].text, group_list[0].group) }, radius: 0, @@ -306,7 +306,7 @@ openerp.web_graph.GraphView = openerp.web.View.extend({ bar_chart.addSeries({ value: "#"+column.group+"#", tooltip:{ - template: _.sprintf("#%s#, %s=#%s#", + template: _.str.sprintf("#%s#, %s=#%s#", self.abscissa, column.text, column.group) }, color: column.color diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index b2d5dcadc25..a3755541548 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -23,6 +23,7 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({ this.form_dialog = new openerp.web.FormDialog(this, {}, this.options.action_views_ids.form, dataset).start(); this.form_dialog.on_form_dialog_saved.add_last(this.do_reload); this.aggregates = {}; + this.group_operators = ['avg', 'max', 'min', 'sum', 'count']; this.qweb = new QWeb2.Engine(); this.qweb.debug = openerp.connection.debug; this.qweb.default_dict = { @@ -58,22 +59,26 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({ this.transform_qweb_template(child); this.qweb.add_template(openerp.web.json_node_to_xml(child)); break; + } else if (child.tag === 'field') { + this.extract_aggregates(child); + } + } + }, + extract_aggregates: function(node) { + for (var j = 0, jj = this.group_operators.length; j < jj; j++) { + if (node.attrs[this.group_operators[j]]) { + this.aggregates[node.attrs.name] = node.attrs[this.group_operators[j]]; + break; } } }, transform_qweb_template: function(node) { - var qweb_prefix = QWeb.prefix, - group_operator = ['avg', 'max', 'min', 'sum', 'count']; + var qweb_prefix = QWeb.prefix; switch (node.tag) { case 'field': node.tag = qweb_prefix; node.attrs[qweb_prefix + '-esc'] = 'record.' + node.attrs['name'] + '.value'; - for (var j = 0, jj = group_operator.length; j < jj; j++) { - if (node.attrs[group_operator[j]]) { - this.aggregates[node.attrs.name] = node.attrs[group_operator[j]]; - break; - } - } + this.extract_aggregates(node); break case 'button': case 'a': @@ -87,13 +92,13 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({ }); if (node.attrs['data-states']) { var states = _.map(node.attrs['data-states'].split(','), function(state) { - return "record.state.raw_value == '" + _.trim(state) + "'"; + return "record.state.raw_value == '" + _.str.trim(state) + "'"; }); node.attrs[qweb_prefix + '-if'] = states.join(' or '); } if (node.attrs['data-kanban_states']) { var states = _.map(node.attrs['data-kanban_states'].split(','), function(state) { - return "record.kanban_state.raw_value == '" + _.trim(state) + "'"; + return "record.kanban_state.raw_value == '" + _.str.trim(state) + "'"; }); node.attrs[qweb_prefix + '-if'] = states.join(' or '); } @@ -372,7 +377,7 @@ openerp.web_kanban.KanbanRecord = openerp.web.Widget.extend({ var self = this, new_record = {}; _.each(record, function(value, name) { - var r = _.clone(self.view.fields_view.fields[name]); + var r = _.clone(self.view.fields_view.fields[name] || {}); r.raw_value = value; r.value = openerp.web.format_value(value, r); new_record[name] = r; @@ -385,7 +390,7 @@ openerp.web_kanban.KanbanRecord = openerp.web.Widget.extend({ widget: this } for (var p in this) { - if (_.startsWith(p, 'kanban_')) { + if (_.str.startsWith(p, 'kanban_')) { ctx[p] = _.bind(this[p], this); } } @@ -497,8 +502,10 @@ openerp.web_kanban.KanbanRecord = openerp.web.Widget.extend({ }, kanban_gravatar: function(email, size) { size = size || 22; + email = _.str.trim(email || '').toLowerCase(); + var default_ = _.str.isBlank(email) ? 'mm' : 'identicon'; var email_md5 = $.md5(email); - return 'http://www.gravatar.com/avatar/' + email_md5 + '.png?s=' + size; + return 'http://www.gravatar.com/avatar/' + email_md5 + '.png?s=' + size + '&d=' + default_; }, kanban_image: function(model, field, id) { id = id || ''; diff --git a/addons/web_kanban/static/src/xml/web_kanban.xml b/addons/web_kanban/static/src/xml/web_kanban.xml index c1346a0b90d..e63d4de3e66 100644 --- a/addons/web_kanban/static/src/xml/web_kanban.xml +++ b/addons/web_kanban/static/src/xml/web_kanban.xml @@ -1,7 +1,9 @@