diff options
| author | Xavier Morel <xmo@openerp.com> | 2011-04-12 15:21:20 +0200 |
|---|---|---|
| committer | Xavier Morel <xmo@openerp.com> | 2011-04-12 15:21:20 +0200 |
| commit | d7da740264e35dd67a7a891770b47d2bc38578ef (patch) | |
| tree | 3c381edfd569b045e959ccd71dbe21dfef141421 /addons/base | |
| parent | b840e25c1d901f0d83c41eef38ada73e8d61bd7b (diff) | |
| parent | b70b02bc5138d7f5f4b70433cb47e28c39f1f953 (diff) | |
[MERGE] from mainline branch, hook in new record handling
bzr revid: xmo@openerp.com-20110412132120-ii3mosez9kguvcjb
Diffstat (limited to 'addons/base')
| -rw-r--r-- | addons/base/controllers/main.py | 6 | ||||
| -rw-r--r-- | addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.js | 1137 | ||||
| -rw-r--r-- | addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.min.js | 18 | ||||
| -rw-r--r-- | addons/base/static/lib/qweb/qweb.js | 4 | ||||
| -rw-r--r-- | addons/base/static/src/base.html | 1 | ||||
| -rw-r--r-- | addons/base/static/src/css/base.css | 181 | ||||
| -rw-r--r-- | addons/base/static/src/img/sideheader-a-bg.png | bin | 0 -> 126 bytes | |||
| -rw-r--r-- | addons/base/static/src/img/toggle-a-bg.png | bin | 0 -> 412 bytes | |||
| -rw-r--r-- | addons/base/static/src/js/chrome.js | 29 | ||||
| -rw-r--r-- | addons/base/static/src/js/data.js | 9 | ||||
| -rw-r--r-- | addons/base/static/src/js/form.js | 313 | ||||
| -rw-r--r-- | addons/base/static/src/js/list.js | 4 | ||||
| -rw-r--r-- | addons/base/static/src/js/search.js | 2 | ||||
| -rw-r--r-- | addons/base/static/src/js/views.js | 16 | ||||
| -rw-r--r-- | addons/base/static/src/xml/base.xml | 58 |
15 files changed, 1631 insertions, 147 deletions
diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 9c9a7f9ac62..8a2c31b42eb 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -386,6 +386,12 @@ class DataSet(openerpweb.Controller): return {'value': value} @openerpweb.jsonrequest + def create(self, req, model, data, context={}): + m = req.session.model(model) + r = m.create(data, context) + return {'result': r} + + @openerpweb.jsonrequest def save(self, req, model, id, data, context={}): m = req.session.model(model) r = m.write([id], data, context) diff --git a/addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.js b/addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.js new file mode 100644 index 00000000000..f251123acd7 --- /dev/null +++ b/addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.js @@ -0,0 +1,1137 @@ +/*! + * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 + * http://benalman.com/projects/jquery-bbq-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +// Script: jQuery BBQ: Back Button & Query Library +// +// *Version: 1.2.1, Last updated: 2/17/2010* +// +// Project Home - http://benalman.com/projects/jquery-bbq-plugin/ +// GitHub - http://github.com/cowboy/jquery-bbq/ +// Source - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.js +// (Minified) - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (4.0kb) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// These working examples, complete with fully commented code, illustrate a few +// ways in which this plugin can be used. +// +// Basic AJAX - http://benalman.com/code/projects/jquery-bbq/examples/fragment-basic/ +// Advanced AJAX - http://benalman.com/code/projects/jquery-bbq/examples/fragment-advanced/ +// jQuery UI Tabs - http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/ +// Deparam - http://benalman.com/code/projects/jquery-bbq/examples/deparam/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, +// Chrome 4-5, Opera 9.6-10.1. +// Unit Tests - http://benalman.com/code/projects/jquery-bbq/unit/ +// +// About: Release History +// +// 1.2.1 - (2/17/2010) Actually fixed the stale window.location Safari bug from +// <jQuery hashchange event> in BBQ, which was the main reason for the +// previous release! +// 1.2 - (2/16/2010) Integrated <jQuery hashchange event> v1.2, which fixes a +// Safari bug, the event can now be bound before DOM ready, and IE6/7 +// page should no longer scroll when the event is first bound. Also +// added the <jQuery.param.fragment.noEscape> method, and reworked the +// <hashchange event (BBQ)> internal "add" method to be compatible with +// changes made to the jQuery 1.4.2 special events API. +// 1.1.1 - (1/22/2010) Integrated <jQuery hashchange event> v1.1, which fixes an +// obscure IE8 EmulateIE7 meta tag compatibility mode bug. +// 1.1 - (1/9/2010) Broke out the jQuery BBQ event.special <hashchange event> +// functionality into a separate plugin for users who want just the +// basic event & back button support, without all the extra awesomeness +// that BBQ provides. This plugin will be included as part of jQuery BBQ, +// but also be available separately. See <jQuery hashchange event> +// plugin for more information. Also added the <jQuery.bbq.removeState> +// method and added additional <jQuery.deparam> examples. +// 1.0.3 - (12/2/2009) Fixed an issue in IE 6 where location.search and +// location.hash would report incorrectly if the hash contained the ? +// character. Also <jQuery.param.querystring> and <jQuery.param.fragment> +// will no longer parse params out of a URL that doesn't contain ? or #, +// respectively. +// 1.0.2 - (10/10/2009) Fixed an issue in IE 6/7 where the hidden IFRAME caused +// a "This page contains both secure and nonsecure items." warning when +// used on an https:// page. +// 1.0.1 - (10/7/2009) Fixed an issue in IE 8. Since both "IE7" and "IE8 +// Compatibility View" modes erroneously report that the browser +// supports the native window.onhashchange event, a slightly more +// robust test needed to be added. +// 1.0 - (10/2/2009) Initial release + +(function($,window){ + '$:nomunge'; // Used by YUI compressor. + + // Some convenient shortcuts. + var undefined, + aps = Array.prototype.slice, + decode = decodeURIComponent, + + // Method / object references. + jq_param = $.param, + jq_param_fragment, + jq_deparam, + jq_deparam_fragment, + jq_bbq = $.bbq = $.bbq || {}, + jq_bbq_pushState, + jq_bbq_getState, + jq_elemUrlAttr, + jq_event_special = $.event.special, + + // Reused strings. + str_hashchange = 'hashchange', + str_querystring = 'querystring', + str_fragment = 'fragment', + str_elemUrlAttr = 'elemUrlAttr', + str_location = 'location', + str_href = 'href', + str_src = 'src', + + // Reused RegExp. + re_trim_querystring = /^.*\?|#.*$/g, + re_trim_fragment = /^.*\#/, + re_no_escape, + + // Used by jQuery.elemUrlAttr. + elemUrlAttr_cache = {}; + + // A few commonly used bits, broken out to help reduce minified file size. + + function is_string( arg ) { + return typeof arg === 'string'; + }; + + // Why write the same function twice? Let's curry! Mmmm, curry.. + + function curry( func ) { + var args = aps.call( arguments, 1 ); + + return function() { + return func.apply( this, args.concat( aps.call( arguments ) ) ); + }; + }; + + // Get location.hash (or what you'd expect location.hash to be) sans any + // leading #. Thanks for making this necessary, Firefox! + function get_fragment( url ) { + return url.replace( /^[^#]*#?(.*)$/, '$1' ); + }; + + // Get location.search (or what you'd expect location.search to be) sans any + // leading #. Thanks for making this necessary, IE6! + function get_querystring( url ) { + return url.replace( /(?:^[^?#]*\?([^#]*).*$)?.*/, '$1' ); + }; + + // Section: Param (to string) + // + // Method: jQuery.param.querystring + // + // Retrieve the query string from a URL or if no arguments are passed, the + // current window.location. + // + // Usage: + // + // > jQuery.param.querystring( [ url ] ); + // + // Arguments: + // + // url - (String) A URL containing query string params to be parsed. If url + // is not passed, the current window.location is used. + // + // Returns: + // + // (String) The parsed query string, with any leading "?" removed. + // + + // Method: jQuery.param.querystring (build url) + // + // Merge a URL, with or without pre-existing query string params, plus any + // object, params string or URL containing query string params into a new URL. + // + // Usage: + // + // > jQuery.param.querystring( url, params [, merge_mode ] ); + // + // Arguments: + // + // url - (String) A valid URL for params to be merged into. This URL may + // contain a query string and/or fragment (hash). + // params - (String) A params string or URL containing query string params to + // be merged into url. + // params - (Object) A params object to be merged into url. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified, and is as-follows: + // + // * 0: params in the params argument will override any query string + // params in url. + // * 1: any query string params in url will override params in the params + // argument. + // * 2: params argument will completely replace any query string in url. + // + // Returns: + // + // (String) Either a params string with urlencoded data or a URL with a + // urlencoded query string in the format 'a=b&c=d&e=f'. + + // Method: jQuery.param.fragment + // + // Retrieve the fragment (hash) from a URL or if no arguments are passed, the + // current window.location. + // + // Usage: + // + // > jQuery.param.fragment( [ url ] ); + // + // Arguments: + // + // url - (String) A URL containing fragment (hash) params to be parsed. If + // url is not passed, the current window.location is used. + // + // Returns: + // + // (String) The parsed fragment (hash) string, with any leading "#" removed. + + // Method: jQuery.param.fragment (build url) + // + // Merge a URL, with or without pre-existing fragment (hash) params, plus any + // object, params string or URL containing fragment (hash) params into a new + // URL. + // + // Usage: + // + // > jQuery.param.fragment( url, params [, merge_mode ] ); + // + // Arguments: + // + // url - (String) A valid URL for params to be merged into. This URL may + // contain a query string and/or fragment (hash). + // params - (String) A params string or URL containing fragment (hash) params + // to be merged into url. + // params - (Object) A params object to be merged into url. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified, and is as-follows: + // + // * 0: params in the params argument will override any fragment (hash) + // params in url. + // * 1: any fragment (hash) params in url will override params in the + // params argument. + // * 2: params argument will completely replace any query string in url. + // + // Returns: + // + // (String) Either a params string with urlencoded data or a URL with a + // urlencoded fragment (hash) in the format 'a=b&c=d&e=f'. + + function jq_param_sub( is_fragment, get_func, url, params, merge_mode ) { + var result, + qs, + matches, + url_params, + hash; + + if ( params !== undefined ) { + // Build URL by merging params into url string. + + // matches[1] = url part that precedes params, not including trailing ?/# + // matches[2] = params, not including leading ?/# + // matches[3] = if in 'querystring' mode, hash including leading #, otherwise '' + matches = url.match( is_fragment ? /^([^#]*)\#?(.*)$/ : /^([^#?]*)\??([^#]*)(#?.*)/ ); + + // Get the hash if in 'querystring' mode, and it exists. + hash = matches[3] || ''; + + if ( merge_mode === 2 && is_string( params ) ) { + // If merge_mode is 2 and params is a string, merge the fragment / query + // string into the URL wholesale, without converting it into an object. + qs = params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' ); + + } else { + // Convert relevant params in url to object. + url_params = jq_deparam( matches[2] ); + + params = is_string( params ) + + // Convert passed params string into object. + ? jq_deparam[ is_fragment ? str_fragment : str_querystring ]( params ) + + // Passed params object. + : params; + + qs = merge_mode === 2 ? params // passed params replace url params + : merge_mode === 1 ? $.extend( {}, params, url_params ) // url params override passed params + : $.extend( {}, url_params, params ); // passed params override url params + + // Convert params object to a string. + qs = jq_param( qs ); + + // Unescape characters specified via $.param.noEscape. Since only hash- + // history users have requested this feature, it's only enabled for + // fragment-related params strings. + if ( is_fragment ) { + qs = qs.replace( re_no_escape, decode ); + } + } + + // Build URL from the base url, querystring and hash. In 'querystring' + // mode, ? is only added if a query string exists. In 'fragment' mode, # + // is always added. + result = matches[1] + ( is_fragment ? '#' : qs || !matches[1] ? '?' : '' ) + qs + hash; + + } else { + // If URL was passed in, parse params from URL string, otherwise parse + // params from window.location. + result = get_func( url !== undefined ? url : window[ str_location ][ str_href ] ); + } + + return result; + }; + + jq_param[ str_querystring ] = curry( jq_param_sub, 0, get_querystring ); + jq_param[ str_fragment ] = jq_param_fragment = curry( jq_param_sub, 1, get_fragment ); + + // Method: jQuery.param.fragment.noEscape + // + // Specify characters that will be left unescaped when fragments are created + // or merged using <jQuery.param.fragment>, or when the fragment is modified + // using <jQuery.bbq.pushState>. This option only applies to serialized data + // object fragments, and not set-as-string fragments. Does not affect the + // query string. Defaults to ",/" (comma, forward slash). + // + // Note that this is considered a purely aesthetic option, and will help to + // create URLs that "look pretty" in the address bar or bookmarks, without + // affecting functionality in any way. That being said, be careful to not + // unescape characters that are used as delimiters or serve a special + // purpose, such as the "#?&=+" (octothorpe, question mark, ampersand, + // equals, plus) characters. + // + // Usage: + // + // > jQuery.param.fragment.noEscape( [ chars ] ); + // + // Arguments: + // + // chars - (String) The characters to not escape in the fragment. If + // unspecified, defaults to empty string (escape all characters). + // + // Returns: + // + // Nothing. + + jq_param_fragment.noEscape = function( chars ) { + chars = chars || ''; + var arr = $.map( chars.split(''), encodeURIComponent ); + re_no_escape = new RegExp( arr.join('|'), 'g' ); + }; + + // A sensible default. These are the characters people seem to complain about + // "uglifying up the URL" the most. + jq_param_fragment.noEscape( ',/' ); + + // Section: Deparam (from string) + // + // Method: jQuery.deparam + // + // Deserialize a params string into an object, optionally coercing numbers, + // booleans, null and undefined values; this method is the counterpart to the + // internal jQuery.param method. + // + // Usage: + // + // > jQuery.deparam( params [, coerce ] ); + // + // Arguments: + // + // params - (String) A params string to be parsed. + // coerce - (Boolean) If true, coerces any numbers or true, false, null, and + // undefined to their actual value. Defaults to false if omitted. + // + // Returns: + // + // (Object) An object representing the deserialized params string. + + $.deparam = jq_deparam = function( params, coerce ) { + var obj = {}, + coerce_types = { 'true': !0, 'false': !1, 'null': null }; + + // Iterate over all name=value pairs. + $.each( params.replace( /\+/g, ' ' ).split( '&' ), function(j,v){ + var param = v.split( '=' ), + key = decode( param[0] ), + val, + cur = obj, + i = 0, + + // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it + // into its component parts. + keys = key.split( '][' ), + keys_last = keys.length - 1; + + // If the first keys part contains [ and the last ends with ], then [] + // are correctly balanced. + if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) { + // Remove the trailing ] from the last keys part. + keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' ); + + // Split first keys part into two parts on the [ and add them back onto + // the beginning of the keys array. + keys = keys.shift().split('[').concat( keys ); + + keys_last = keys.length - 1; + } else { + // Basic 'foo' style key. + keys_last = 0; + } + + // Are we dealing with a name=value pair, or just a name? + if ( param.length === 2 ) { + val = decode( param[1] ); + + // Coerce values. + if ( coerce ) { + val = val && !isNaN(val) ? +val // number + : val === 'undefined' ? undefined // undefined + : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null + : val; // string + } + + if ( keys_last ) { + // Complex key, build deep object structure based on a few rules: + // * The 'cur' pointer starts at the object top-level. + // * [] = array push (n is set to array length), [n] = array if n is + // numeric, otherwise object. + // * If at the last keys part, set the value. + // * For each keys part, if the current level is undefined create an + // object or array based on the type of the next keys part. + // * Move the 'cur' pointer to the next level. + // * Rinse & repeat. + for ( ; i <= keys_last; i++ ) { + key = keys[i] === '' ? cur.length : keys[i]; + cur = cur[key] = i < keys_last + ? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] ) + : val; + } + + } else { + // Simple key, even simpler rules, since only scalars and shallow + // arrays are allowed. + + if ( $.isArray( obj[key] ) ) { + // val is already an array, so push on the next value. + obj[key].push( val ); + + } else if ( obj[key] !== undefined ) { + // val isn't an array, but since a second value has been specified, + // convert val into an array. + obj[key] = [ obj[key], val ]; + + } else { + // val is a scalar. + obj[key] = val; + } + } + + } else if ( key ) { + // No value was defined, so set something meaningful. + obj[key] = coerce + ? undefined + : ''; + } + }); + + return obj; + }; + + // Method: jQuery.deparam.querystring + // + // Parse the query string from a URL or the current window.location, + // deserializing it into an object, optionally coercing numbers, booleans, + // null and undefined values. + // + // Usage: + // + // > jQuery.deparam.querystring( [ url ] [, coerce ] ); + // + // Arguments: + // + // url - (String) An optional params string or URL containing query string + // params to be parsed. If url is omitted, the current window.location + // is used. + // coerce - (Boolean) If true, coerces any numbers or true, false, null, and + // undefined to their actual value. Defaults to false if omitted. + // + // Returns: + // + // (Object) An object representing the deserialized params string. + + // Method: jQuery.deparam.fragment + // + // Parse the fragment (hash) from a URL or the current window.location, + // deserializing it into an object, optionally coercing numbers, booleans, + // null and undefined values. + // + // Usage: + // + // > jQuery.deparam.fragment( [ url ] [, coerce ] ); + // + // Arguments: + // + // url - (String) An optional params string or URL containing fragment (hash) + // params to be parsed. If url is omitted, the current window.location + // is used. + // coerce - (Boolean) If true, coerces any numbers or true, false, null, and + // undefined to their actual value. Defaults to false if omitted. + // + // Returns: + // + // (Object) An object representing the deserialized params string. + + function jq_deparam_sub( is_fragment, url_or_params, coerce ) { + if ( url_or_params === undefined || typeof url_or_params === 'boolean' ) { + // url_or_params not specified. + coerce = url_or_params; + url_or_params = jq_param[ is_fragment ? str_fragment : str_querystring ](); + } else { + url_or_params = is_string( url_or_params ) + ? url_or_params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' ) + : url_or_params; + } + + return jq_deparam( url_or_params, coerce ); + }; + + jq_deparam[ str_querystring ] = curry( jq_deparam_sub, 0 ); + jq_deparam[ str_fragment ] = jq_deparam_fragment = curry( jq_deparam_sub, 1 ); + + // Section: Element manipulation + // + // Method: jQuery.elemUrlAttr + // + // Get the internal "Default URL attribute per tag" list, or augment the list + // with additional tag-attribute pairs, in case the defaults are insufficient. + // + // In the <jQuery.fn.querystring> and <jQuery.fn.fragment> methods, this list + // is used to determine which attribute contains the URL to be modified, if + // an "attr" param is not specified. + // + // Default Tag-Attribute List: + // + // a - href + // base - href + // iframe - src + // img - src + // input - src + // form - action + // link - href + // script - src + // + // Usage: + // + // > jQuery.elemUrlAttr( [ tag_attr ] ); + // + // Arguments: + // + // tag_attr - (Object) An object containing a list of tag names and their + // associated default attribute names in the format { tag: 'attr', ... } to + // be merged into the internal tag-attribute list. + // + // Returns: + // + // (Object) An object containing all stored tag-attribute values. + + // Only define function and set defaults if function doesn't already exist, as + // the urlInternal plugin will provide this method as well. + $[ str_elemUrlAttr ] || ($[ str_elemUrlAttr ] = function( obj ) { + return $.extend( elemUrlAttr_cache, obj ); + })({ + a: str_href, + base: str_href, + iframe: str_src, + img: str_src, + input: str_src, + form: 'action', + link: str_href, + script: str_src + }); + + jq_elemUrlAttr = $[ str_elemUrlAttr ]; + + // Method: jQuery.fn.querystring + // + // Update URL attribute in one or more elements, merging the current URL (with + // or without pre-existing query string params) plus any params object or + // string into a new URL, which is then set into that attribute. Like + // <jQuery.param.querystring (build url)>, but for all elements in a jQuery + // collection. + // + // Usage: + // + // > jQuery('selector').querystring( [ attr, ] params [, merge_mode ] ); + // + // Arguments: + // + // attr - (String) Optional name of an attribute that will contain a URL to + // merge params or url into. See <jQuery.elemUrlAttr> for a list of default + // attributes. + // params - (Object) A params object to be merged into the URL attribute. + // params - (String) A URL containing query string params, or params string + // to be merged into the URL attribute. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified, and is as-follows: + // + // * 0: params in the params argument will override any params in attr URL. + // * 1: any params in attr URL will override params in the params argument. + // * 2: params argument will completely replace any query string in attr + // URL. + // + // Returns: + // + // (jQuery) The initial jQuery collection of elements, but with modified URL + // attribute values. + + // Method: jQuery.fn.fragment + // + // Update URL attribute in one or more elements, merging the current URL (with + // or without pre-existing fragment/hash params) plus any params object or + // string into a new URL, which is then set into that attribute. Like + // <jQuery.param.fragment (build url)>, but for all elements in a jQuery + // collection. + // + // Usage: + // + // > jQuery('selector').fragment( [ attr, ] params [, merge_mode ] ); + // + // Arguments: + // + // attr - (String) Optional name of an attribute that will contain a URL to + // merge params into. See <jQuery.elemUrlAttr> for a list of default + // attributes. + // params - (Object) A params object to be merged into the URL attribute. + // params - (String) A URL containing fragment (hash) params, or params + // string to be merged into the URL attribute. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified, and is as-follows: + // + // * 0: params in the params argument will override any params in attr URL. + // * 1: any params in attr URL will override params in the params argument. + // * 2: params argument will completely replace any fragment (hash) in attr + // URL. + // + // Returns: + // + // (jQuery) The initial jQuery collection of elements, but with modified URL + // attribute values. + + function jq_fn_sub( mode, force_attr, params, merge_mode ) { + if ( !is_string( params ) && typeof params !== 'object' ) { + // force_attr not specified. + merge_mode = params; + params = force_attr; + force_attr = undefined; + } + + return this.each(function(){ + var that = $(this), + + // Get attribute specified, or default specified via $.elemUrlAttr. + attr = force_attr || jq_elemUrlAttr()[ ( this.nodeName || '' ).toLowerCase() ] || '', + + // Get URL value. + url = attr && that.attr( attr ) || ''; + + // Update attribute with new URL. + that.attr( attr, jq_param[ mode ]( url, params, merge_mode ) ); + }); + + }; + + $.fn[ str_querystring ] = curry( jq_fn_sub, str_querystring ); + $.fn[ str_fragment ] = curry( jq_fn_sub, str_fragment ); + + // Section: History, hashchange event + // + // Method: jQuery.bbq.pushState + // + // Adds a 'state' into the browser history at the current position, setting + // location.hash and triggering any bound <hashchange event> callbacks + // (provided the new state is different than the previous state). + // + // If no arguments are passed, an empty state is created, which is just a + // shortcut for jQuery.bbq.pushState( {}, 2 ). + // + // Usage: + // + // > jQuery.bbq.pushState( [ params [, merge_mode ] ] ); + // + // Arguments: + // + // params - (String) A serialized params string or a hash string beginning + // with # to merge into location.hash. + // params - (Object) A params object to merge into location.hash. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified (unless a hash string beginning with # is specified, in which + // case merge behavior defaults to 2), and is as-follows: + // + // * 0: params in the params argument will override any params in the + // current state. + // * 1: any params in the current state will override params in the params + // argument. + // * 2: params argument will completely replace current state. + // + // Returns: + // + // Nothing. + // + // Additional Notes: + // + // * Setting an empty state may cause the browser to scroll. + // * Unlike the fragment and querystring methods, if a hash string beginning + // with # is specified as the params agrument, merge_mode defaults to 2. + + jq_bbq.pushState = jq_bbq_pushState = function( params, merge_mode ) { + if ( is_string( params ) && /^#/.test( params ) && merge_mode === undefined ) { + // Params string begins with # and merge_mode not specified, so completely + // overwrite window.location.hash. + merge_mode = 2; + } + + var has_args = params !== undefined, + // Merge params into window.location using $.param.fragment. + url = jq_param_fragment( window[ str_location ][ str_href ], + has_args ? params : {}, has_args ? merge_mode : 2 ); + + // Set new window.location.href. If hash is empty, use just # to prevent + // browser from reloading the page. Note that Safari 3 & Chrome barf on + // location.hash = '#'. + window[ str_location ][ str_href ] = url + ( /#/.test( url ) ? '' : '#' ); + }; + + // Method: jQuery.bbq.getState + // + // Retrieves the current 'state' from the browser history, parsing + // location.hash for a specific key or returning an object containing the + // entire state, optionally coercing numbers, booleans, null and undefined + // values. + // + // Usage: + // + // > jQuery.bbq.getState( [ key ] [, coerce ] ); + // + // Arguments: + // + // key - (String) An optional state key for which to return a value. + // coerce - (Boolean) If true, coerces any numbers or true, false, null, and + // undefined to their actual value. Defaults to false. + // + // Returns: + // + // (Anything) If key is passed, returns the value corresponding with that key + // in the location.hash 'state', or undefined. If not, an object + // representing the entire 'state' is returned. + + jq_bbq.getState = jq_bbq_getState = function( key, coerce ) { + return key === undefined || typeof key === 'boolean' + ? jq_deparam_fragment( key ) // 'key' really means 'coerce' here + : jq_deparam_fragment( coerce )[ key ]; + }; + + // Method: jQuery.bbq.removeState + // + // Remove one or more keys from the current browser history 'state', creating + // a new state, setting location.hash and triggering any bound + // <hashchange event> callbacks (provided the new state is different than + // the previous state). + // + // If no arguments are passed, an empty state is created, which is just a + // shortcut for jQuery.bbq.pushState( {}, 2 ). + // + // Usage: + // + // > jQuery.bbq.removeState( [ key [, key ... ] ] ); + // + // Arguments: + // + // key - (String) One or more key values to remove from the current state, + // passed as individual arguments. + // key - (Array) A single array argument that contains a list of key values + // to remove from the current state. + // + // Returns: + // + // Nothing. + // + // Additional Notes: + // + // * Setting an empty state may cause the browser to scroll. + + jq_bbq.removeState = function( arr ) { + var state = {}; + + // If one or more arguments is passed.. + if ( arr !== undefined ) { + + // Get the current state. + state = jq_bbq_getState(); + + // For each passed key, delete the corresponding property from the current + // state. + $.each( $.isArray( arr ) ? arr : arguments, function(i,v){ + delete state[ v ]; + }); + } + + // Set the state, completely overriding any existing state. + jq_bbq_pushState( state, 2 ); + }; + + // Event: hashchange event (BBQ) + // + // Usage in jQuery 1.4 and newer: + // + // In jQuery 1.4 and newer, the event object passed into any hashchange event + // callback is augmented with a copy of the location.hash fragment at the time + // the event was triggered as its event.fragment property. In addition, the + // event.getState method operates on this property (instead of location.hash) + // which allows this fragment-as-a-state to be referenced later, even after + // window.location may have changed. + // + // Note that event.fragment and event.getState are not defined according to + // W3C (or any other) specification, but will still be available whether or + // not the hashchange event exists natively in the browser, because of the + // utility they provide. + // + // The event.fragment property contains the output of <jQuery.param.fragment> + // and the event.getState method is equivalent to the <jQuery.bbq.getState> + // method. + // + // > $(window).bind( 'hashchange', function( event ) { + // > var hash_str = event.fragment, + // > param_obj = event.getState(), + // > param_val = event.getState( 'param_name' ), + // > param_val_coerced = event.getState( 'param_name', true ); + // > ... + // > }); + // + // Usage in jQuery 1.3.2: + // + // In jQuery 1.3.2, the event object cannot to be augmented as in jQuery 1.4+, + // so the fragment state isn't bound to the event object and must instead be + // parsed using the <jQuery.param.fragment> and <jQuery.bbq.getState> methods. + // + // > $(window).bind( 'hashchange', function( event ) { + // > var hash_str = $.param.fragment(), + // > param_obj = $.bbq.getState(), + // > param_val = $.bbq.getState( 'param_name' ), + // > param_val_coerced = $.bbq.getState( 'param_name', true ); + // > ... + // > }); + // + // Additional Notes: + // + // * Due to changes in the special events API, jQuery BBQ v1.2 or newer is + // required to enable the augmented event object in jQuery 1.4.2 and newer. + // * See <jQuery hashchange event> for more detailed information. + + jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], { + + // Augmenting the event object with the .fragment property and .getState + // method requires jQuery 1.4 or newer. Note: with 1.3.2, everything will + // work, but the event won't be augmented) + add: function( handleObj ) { + var old_handler; + + function new_handler(e) { + // e.fragment is set to the value of location.hash (with any leading # + // removed) at the time the event is triggered. + var hash = e[ str_fragment ] = jq_param_fragment(); + + // e.getState() works just like $.bbq.getState(), but uses the + // e.fragment property stored on the event object. + e.getState = function( key, coerce ) { + return key === undefined || typeof key === 'boolean' + ? jq_deparam( hash, key ) // 'key' really means 'coerce' here + : jq_deparam( hash, coerce )[ key ]; + }; + + old_handler.apply( this, arguments ); + }; + + // This may seem a little complicated, but it normalizes the special event + // .add method between jQuery 1.4/1.4.1 and 1.4.2+ + if ( $.isFunction( handleObj ) ) { + // 1.4, 1.4.1 + old_handler = handleObj; + return new_handler; + } else { + // 1.4.2+ + old_handler = handleObj.handler; + handleObj.handler = new_handler; + } + } + + }); + +})(jQuery,this); + +/*! + * jQuery hashchange event - v1.2 - 2/11/2010 + * http://benalman.com/projects/jquery-hashchange-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +// Script: jQuery hashchange event +// +// *Version: 1.2, Last updated: 2/11/2010* +// +// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ +// GitHub - http://github.com/cowboy/jquery-hashchange/ +// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js +// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (1.1kb) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// This working example, complete with fully commented code, illustrate one way +// in which this plugin can be used. +// +// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.1. +// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ +// +// About: Known issues +// +// While this jQuery hashchange event implementation is quite stable and robust, +// there are a few unfortunate browser bugs surrounding expected hashchange +// event-based behaviors, independent of any JavaScript window.onhashchange +// abstraction. See the following examples for more information: +// +// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ +// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ +// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ +// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ +// +// About: Release History +// +// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin +// from a page on another domain would cause an error in Safari 4. Also, +// IE6/7 Iframe is now inserted after the body (this actually works), +// which prevents the page from scrolling when the event is first bound. +// Event can also now be bound before DOM ready, but it won't be usable +// before then in IE6/7. +// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug +// where browser version is incorrectly reported as 8.0, despite +// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. +// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special +// window.onhashchange functionality into a separate plugin for users +// who want just the basic event & back button support, without all the +// extra awesomeness that BBQ provides. This plugin will be included as +// part of jQuery BBQ, but also be available separately. + +(function($,window,undefined){ + '$:nomunge'; // Used by YUI compressor. + + // Method / object references. + var fake_onhashchange, + jq_event_special = $.event.special, + + // Reused strings. + str_location = 'location', + str_hashchange = 'hashchange', + str_href = 'href', + + // IE6/7 specifically need some special love when it comes to back-button + // support, so let's do a little browser sniffing.. + browser = $.browser, + mode = document.documentMode, + is_old_ie = browser.msie && ( mode === undefined || mode < 8 ), + + // Does the browser support window.onhashchange? Test for IE version, since + // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"! + supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie; + + // Get location.hash (or what you'd expect location.hash to be) sans any + // leading #. Thanks for making this necessary, Firefox! + function get_fragment( url ) { + url = url || window[ str_location ][ str_href ]; + return url.replace( /^[^#]*#?(.*)$/, '$1' ); + }; + + // Property: jQuery.hashchangeDelay + // + // The numeric interval (in milliseconds) at which the <hashchange event> + // polling loop executes. Defaults to 100. + + $[ str_hashchange + 'Delay' ] = 100; + + // Event: hashchange event + // + // Fired when location.hash changes. In browsers that support it, the native + // window.onhashchange event is used (IE8, FF3.6), otherwise a polling loop is + // initialized, running every <jQuery.hashchangeDelay> milliseconds to see if + // the hash has changed. In IE 6 and 7, a hidden Iframe is created to allow + // the back button and hash-based history to work. + // + // Usage: + // + // > $(window).bind( 'hashchange', function(e) { + // > var hash = location.hash; + // > ... + // > }); + // + // Additional Notes: + // + // * The polling loop and Iframe are not created until at least one callback + // is actually bound to 'hashchange'. + // * If you need the bound callback(s) to execute immediately, in cases where + // the page 'state' exists on page load (via bookmark or page refresh, for + // example) use $(window).trigger( 'hashchange' ); + // * The event can be bound before DOM ready, but since it won't be usable + // before then in IE6/7 (due to the necessary Iframe), recommended usage is + // to bind it inside a $(document).ready() callback. + + jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], { + + // Called only when the first 'hashchange' event is bound to window. + setup: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to create our own. And we don't want to call this + // until the user binds to the event, just in case they never do, since it + // will create a polling loop and possibly even a hidden Iframe. + $( fake_onhashchange.start ); + }, + + // Called only when the last 'hashchange' event is unbound from window. + teardown: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to stop ours (if possible). + $( fake_onhashchange.stop ); + } + + }); + + // fake_onhashchange does all the work of triggering the window.onhashchange + // event for browsers that don't natively support it, including creating a + // polling loop to watch for hash changes and in IE 6/7 creating a hidden + // Iframe to enable back and forward. + fake_onhashchange = (function(){ + var self = {}, + timeout_id, + iframe, + set_history, + get_history; + + // Initialize. In IE 6/7, creates a hidden Iframe for history handling. + function init(){ + // Most browsers don't need special methods here.. + set_history = get_history = function(val){ return val; }; + + // But IE6/7 do! + if ( is_old_ie ) { + + // Create hidden Iframe after the end of the body to prevent initial + // page load from scrolling unnecessarily. + iframe = $('<iframe src="javascript:0"/>').hide().insertAfter( 'body' )[0].contentWindow; + + // Get history by looking at the hidden Iframe's location.hash. + get_history = function() { + return get_fragment( iframe.document[ str_location ][ str_href ] ); + }; + + // Set a new history item by opening and then closing the Iframe + // document, *then* setting its location.hash. + set_history = function( hash, history_hash ) { + if ( hash !== history_hash ) { + var doc = iframe.document; + doc.open().close(); + doc[ str_location ].hash = '#' + hash; + } + }; + + // Set initial history. + set_history( get_fragment() ); + } + }; + + // Start the polling loop. + self.start = function() { + // Polling loop is already running! + if ( timeout_id ) { return; } + + // Remember the initial hash so it doesn't get triggered immediately. + var last_hash = get_fragment(); + + // Initialize if not yet initialized. + set_history || init(); + + // This polling loop checks every $.hashchangeDelay milliseconds to see if + // location.hash has changed, and triggers the 'hashchange' event on + // window when necessary. + (function loopy(){ + var hash = get_fragment(), + history_hash = get_history( last_hash ); + + if ( hash !== last_hash ) { + set_history( last_hash = hash, history_hash ); + + $(window).trigger( str_hashchange ); + + } else if ( history_hash !== last_hash ) { + window[ str_location ][ str_href ] = window[ str_location ][ str_href ].replace( /#.*/, '' ) + '#' + history_hash; + } + + timeout_id = setTimeout( loopy, $[ str_hashchange + 'Delay' ] ); + })(); + }; + + // Stop the polling loop, but only if an IE6/7 Iframe wasn't created. In + // that case, even if there are no longer any bound event handlers, the + // polling loop is still necessary for back/next to work at all! + self.stop = function() { + if ( !iframe ) { + timeout_id && clearTimeout( timeout_id ); + timeout_id = 0; + } + }; + + return self; + })(); + +})(jQuery,this); diff --git a/addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.min.js b/addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.min.js new file mode 100644 index 00000000000..bcbf24834ac --- /dev/null +++ b/addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.min.js @@ -0,0 +1,18 @@ +/* + * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 + * http://benalman.com/projects/jquery-bbq-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M<N?O[P]||(R[M+1]&&isNaN(R[M+1])?{}:[]):J}}else{if($.isArray(H[P])){H[P].push(J)}else{if(H[P]!==i){H[P]=[H[P],J]}else{H[P]=J}}}}else{if(P){H[P]=F?i:""}}});return H};function z(H,F,G){if(F===i||typeof F==="boolean"){G=F;F=a[H?D:A]()}else{F=E(F)?F.replace(H?w:x,""):F}return l(F,G)}l[A]=B(z,0);l[D]=v=B(z,1);$[y]||($[y]=function(F){return $.extend(C,F)})({a:k,base:k,iframe:t,img:t,input:t,form:"action",link:k,script:t});j=$[y];function s(I,G,H,F){if(!E(H)&&typeof H!=="object"){F=H;H=G;G=i}return this.each(function(){var L=$(this),J=G||j()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,F))})}$.fn[A]=B(s,A);$.fn[D]=B(s,D);b.pushState=q=function(I,F){if(E(I)&&/^#/.test(I)&&F===i){F=2}var H=I!==i,G=c(p[g][k],H?I:{},H?F:2);p[g][k]=G+(/#/.test(G)?"":"#")};b.getState=u=function(F,G){return F===i||typeof F==="boolean"?v(F):v(G)[F]};b.removeState=function(F){var G={};if(F!==i){G=u();$.each($.isArray(F)?F:arguments,function(I,H){delete G[H]})}q(G,2)};e[d]=$.extend(e[d],{add:function(F){var H;function G(J){var I=J[D]=c();J.getState=function(K,L){return K===i||typeof K==="boolean"?l(I,K):l(I,L)[K]};H.apply(this,arguments)}if($.isFunction(F)){H=F;return G}else{H=F.handler;F.handler=G}}})})(jQuery,this); +/* + * jQuery hashchange event - v1.2 - 2/11/2010 + * http://benalman.com/projects/jquery-hashchange-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);
\ No newline at end of file diff --git a/addons/base/static/lib/qweb/qweb.js b/addons/base/static/lib/qweb/qweb.js index 59bcd12ef9b..05dd9d65fe2 100644 --- a/addons/base/static/lib/qweb/qweb.js +++ b/addons/base/static/lib/qweb/qweb.js @@ -166,7 +166,9 @@ var QWeb = { for (var an in g_att) { att += " " + an + '="' + this.escape_att(g_att[an]) + '"'; } - return inner.length ? "<" + e.tagName + att + ">" + inner + "</" + e.tagName + ">" : "<" + e.tagName + att + "/>"; + // Some IE versions have problems with closed tags + var opentag = !!t_att['opentag'] && this.eval_bool(t_att["opentag"], v); + return inner.length || opentag ? "<" + e.tagName + att + ">" + inner + "</" + e.tagName + ">" : "<" + e.tagName + att + "/>"; }, render_att_att:function(e, t_att, g_att, v, ext, av) { if (ext) { diff --git a/addons/base/static/src/base.html b/addons/base/static/src/base.html index 3c8f76a6e23..bd717d32f34 100644 --- a/addons/base/static/src/base.html +++ b/addons/base/static/src/base.html @@ -15,6 +15,7 @@ <script type="text/javascript" src="/base/static/lib/jquery.ui.notify/js/jquery.notify.js"></script> <script type="text/javascript" src="/base/static/lib/jquery.superfish/js/hoverIntent.js"></script> <script type="text/javascript" src="/base/static/lib/jquery.superfish/js/superfish.js"></script> + <script type="text/javascript" src="/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script> <script type="text/javascript" src="/base/static/src/js/base.js"></script> <script type="text/javascript" src="/base/static/src/js/chrome.js"></script> <script type="text/javascript" src="/base/static/src/js/data.js"></script> diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index ee15d02f3f7..b1ed332da5d 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -172,7 +172,10 @@ body.openerp { padding: 0 0 2px; border: none; background: none; - padding-left: 20px; + padding-left: 10px; +} +.openerp .secondary_menu h3 span, .openerp .secondary_menu h4 span { + left: 0 !important; } .openerp .secondary_menu a { display: block; @@ -201,6 +204,9 @@ body.openerp { text-shadow: #fff 0 1px 0; border: none !important; } +.openerp div.submenu_accordion div.menu_content a span { + padding-left: 20px; +} /* Header */ .openerp .header { @@ -305,16 +311,39 @@ body.openerp { } /* Footer */ -.openerp .footer { +.openerp div.oe_footer { + background: none repeat scroll 0 0 #CCCCCC; + overflow: hidden; + padding: 5px 0; + position: relative; + -moz-box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.4); + -webkit-box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.4); + box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.4); +} +.openerp div.oe_footer p.oe_footer_powered { + left: 50%; + margin: 0; + padding: 0 15px; + color: #666666; + font-weight: bold; + font-size: 0.8em; + font-family: Ubuntu, Helvetica, sans-serif; text-align: center; } +.openerp div.oe_footer p.oe_footer_powered a { + text-decoration: none; + color: #666666; +} /* Main Application */ .openerp .application { - padding: 6px; + padding: 0px; } .openerp h2.oe_view_title { + font-size: 175%; + font-weight: normal; + font-family: Ubuntu, Helvetica, sans-serif; margin: 2px 0; color: #252424; text-shadow: white 0 1px 0; @@ -479,8 +508,37 @@ body.openerp { border-bottom-right-radius: 7px; } +/* Notebook */ +.openerp .oe_form_notebook { + padding: 0px; + background: none; + border-width: 0px; +} +.openerp .oe_form_notebook ul.ui-tabs-nav { + padding-left: 0px; + background: transparent; + border-width: 0px 0px 1px 0px; + border-radius: 0px; + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + line-height: 0.5em; +} +.openerp .oe_form_notebook ul.ui-tabs-nav li { + font-weight: bold; +} +.openerp .oe_form_notebook .ui-tabs-panel { + background: #f9f9f9; + border-width: 0px 1px 1px 1px; +} +.openerp .oe_form_notebook .ui-tabs-selected { + background: #f9f9f9; +} /* Form */ +.openerp table.oe_frame td { + color: #4c4c4c; + font-weight: 80%; +} .openerp .required.error { border: 1px solid #900; } @@ -491,6 +549,22 @@ body.openerp { float: right; } +.openerp label.oe_form_label_help { + cursor: help; +} +.openerp label.oe_form_label, .openerp label.oe_form_label_help { + display: block; + text-align: right; +} +.openerp label.oe_form_label_help span { + font-size: 80%; + color: darkgreen; + vertical-align:top; + position: relative; + top: -4px; + padding: 0 2px; +} + /* Inputs */ .openerp input[type="text"], .openerp input[type="password"], .openerp select, .openerp textarea { -moz-box-sizing: border-box; @@ -503,11 +577,14 @@ body.openerp { -webkit-border-radius: 3px; border-radius: 3px; background: white; + min-width: 90px; + color: #1f1f1f; } .openerp input[type="text"], .openerp input[type="password"], .openerp select, .openerp .button { height: 22px; } .openerp .button { + color: #4c4c4c; white-space: nowrap; } .openerp .button span { @@ -543,37 +620,119 @@ body.openerp { .openerp .ui-widget { font-size: 1em; } +.openerp .oe_form_field_progressbar .ui-progressbar { + height: 22px; + font-size: 10px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + border: 1px solid #999; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background: white; + min-width: 90px; +} /* Sidebar */ .openerp .view-manager-main-table { + margin: 0; width:100%; + border-collapse:collapse; + height:100%; } -.openerp .view-manager-main-sidebar { + +.openerp .view-manager-main-table tbody { vertical-align: top; +} + +.openerp .view-manager-main-content { + padding: 6px; +} + +.openerp .view-manager-main-sidebar { width:0; padding:0; margin:0; } -.openerp .view-manager-main-sidebar .sidebar-main-div { +.openerp .sidebar-main-div { + height:100%; +} + +.openerp .sidebar-sub-div { padding:0; margin:0; + width:180px; + border-left-color: #D2CFCF; + border-left-style: solid; + border-left-width: 1px; + height:100%; + font-family: Ubuntu, Helvetica, sans-serif; + font-size: 0.9em; +} + +.openerp .closed-sidebar .sidebar-sub-div { + width:22px; } -.openerp .view-manager-main-sidebar .sidebar-sub-div { - border: solid 1px #1F1F1F; - border-bottom-width: 0; +.openerp .closed-sidebar .sidebar-displaying-div { + display:none; +} + +.openerp .sidebar-main-div a { + color: #555; + text-decoration: none; +} + +.openerp .sidebar-main-div a:hover { + color: black; +} + +.openerp .view-manager-main-sidebar h2 { + margin:0; + font-family: Ubuntu, Helvetica, sans-serif; + font-size: 1.15em; + color: #8E8E8E; + text-shadow: white 0px 1px 0px; + padding-left: 10px; + padding-right: 21px; + height: 21px; + background: url(../img/sideheader-a-bg.png) repeat-x; + border-color: #D2CFCF; + border-style: solid; + border-top-width: 1px; + border-bottom-width: 1px; + border-left-width: 0; + border-right-width: 0; } .openerp .view-manager-main-sidebar ul { list-style-type: none; margin: 0; padding: 0; - width: 120px; - border-bottom: solid 1px #1F1F1F; + display: block; } .openerp .view-manager-main-sidebar li { - padding: 3px; + display: block; + padding: 3px 3px 3px 10px; +} + +.openerp .toggle-sidebar { + border-color: #D2CFCF; + border-style: solid; + border-width: 1px; + display: block; + background: url(../img/toggle-a-bg.png); + width: 21px; + height: 21px; + position: absolute; + z-index: 10; +} +.openerp .open-sidebar .toggle-sidebar { + margin-left: 158px; + background-position: 21px 0; } diff --git a/addons/base/static/src/img/sideheader-a-bg.png b/addons/base/static/src/img/sideheader-a-bg.png Binary files differnew file mode 100644 index 00000000000..ca9b64b8b01 --- /dev/null +++ b/addons/base/static/src/img/sideheader-a-bg.png diff --git a/addons/base/static/src/img/toggle-a-bg.png b/addons/base/static/src/img/toggle-a-bg.png Binary files differnew file mode 100644 index 00000000000..b9837ab233e --- /dev/null +++ b/addons/base/static/src/img/toggle-a-bg.png diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index b26dccffb2e..bbfe37661cf 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -245,12 +245,7 @@ openerp.base.Notification = openerp.base.BasicController.extend({ text: text }); }, - // TODO remove to avoid default as attribute - 'default': function(title, text) { - this.notify(title,text); - }, - // TODO change into warn to avoid alert - alert: function(title, text) { + warn: function(title, text) { this.$element.notify('create', 'oe_notification_alert', { title: title, text: text @@ -478,8 +473,10 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b continue; openerp[mod] = {}; // init module mod - openerp._openerp[mod](openerp); - self.module_loaded[mod] = true; + if(openerp._openerp[mod] != undefined) { + openerp._openerp[mod](openerp); + self.module_loaded[mod] = true; + } } } }); @@ -783,6 +780,10 @@ openerp.base.Header = openerp.base.Controller.extend({ this.do_update(); }, do_update: function() { + if(jQuery.param != undefined && + jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { + this.kitten = 1; + } this.$element.html(QWeb.render("Header", this)); } }); @@ -907,11 +908,21 @@ openerp.base.WebClient = openerp.base.Controller.extend({ this.header.start(); this.login.start(); this.menu.start(); - this.notification['default']("OpenERP Client", "The openerp client has been initialized."); + this.notification.notify("OpenERP Client", "The openerp client has been initialized."); }, on_logged: function() { this.action = new openerp.base.ActionManager(this.session, "oe_app"); this.action.start(); + + // if using saved actions, load the action and give it to action manager + var parameters = jQuery.deparam(jQuery.param.querystring()); + if(parameters["s_action"] != undefined) { + var key = parseInt(parameters["s_action"]); + var self = this; + this.rpc("/base/session/get_session_action", {key:key}, function(action) { + self.action.do_action(action); + }); + } }, on_menu_action: function(action) { this.action.do_action(action); diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index 8f5d6168e75..7b1a930c88f 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -89,7 +89,12 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base. context: this.context }, callback); }, - create: function() { + create: function(data, callback) { + return this.rpc('/base/dataset/create', { + model: this.model, + data: data, + context: this.context + }, callback); }, write: function (id, data, callback) { return this.rpc('/base/dataset/save', { @@ -100,7 +105,7 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base. }, callback); }, unlink: function(ids) { - this.notification['default']("Unlink", ids); + this.notification.notify("Unlink", ids); }, call: function (method, ids, args, callback) { ids = ids || []; diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index d33c9f2ee7e..e54c46bb533 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -27,6 +27,7 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base this.fields = {}; this.datarecord = {}; this.ready = false; + this.show_invalid = true; }, start: function() { //this.log('Starting FormView '+this.model+this.view_id) @@ -48,10 +49,10 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base self.on_pager_action(action); }); - this.$element.find('div.oe_form_buttons button.oe_form_button_save').click(this.do_save); - this.$element.find('div.oe_form_buttons button.oe_form_button_save_edit').click(this.do_save_edit); - this.$element.find('div.oe_form_buttons button.oe_form_button_cancel').click(this.do_cancel); - this.$element.find('div.oe_form_buttons button.oe_form_button_new').click(this.on_button_new); + this.$element.find('#' + this.element_id + '_header button.oe_form_button_save').click(this.do_save); + this.$element.find('#' + this.element_id + '_header button.oe_form_button_save_edit').click(this.do_save_edit); + this.$element.find('#' + this.element_id + '_header button.oe_form_button_cancel').click(this.do_cancel); + this.$element.find('#' + this.element_id + '_header button.oe_form_button_new').click(this.on_button_new); // sidebar stuff if (this.view_manager.sidebar) { @@ -59,8 +60,19 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base } }, do_show: function () { - this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); - this.$element.show(); + var self = this; + this.do_update_pager.add({ + unique: true, + callback: function() { + self.$element.show(); + } + }); + if (this.dataset.index === null) { + // null index means we should start a new record + this.on_button_new(); + } else { + this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); + } }, do_hide: function () { this.$element.hide(); @@ -69,20 +81,22 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base if (record) { this.datarecord = record; for (var f in this.fields) { - this.fields[f].set_value(this.datarecord[f]); + this.fields[f].set_value(this.datarecord[f] || false); + this.fields[f].validate(); } if (!record.id) { + this.show_invalid = false; // Second pass in order to trigger the onchanges in case of new record for (var f in record) { - this.fields[f].on_ui_change(); + this.on_form_changed(this.fields[f]); } } this.on_form_changed(); - this.ready = true; + this.show_invalid = this.ready = true; } else { this.log("No record received"); } - this.do_update_pager(); + this.do_update_pager(record.id == null); }, on_form_changed: function(widget) { if (widget && widget.node.attrs.on_change) { @@ -110,14 +124,13 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base this.dataset.index = this.dataset.ids.length - 1; break; } - this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); + this.reload(); }, - do_update_pager: function() { - var $pager = this.$element.find('div.oe_form_pager'); - $pager.find("button[data-pager-action='first'], button[data-pager-action='previous']").attr('disabled', this.dataset.index == 0); - $pager.find("button[data-pager-action='next'], button[data-pager-action='last']").attr('disabled', this.dataset.index == this.dataset.ids.length - 1); - this.$element.find('span.oe_pager_index').html(this.dataset.index + 1); - this.$element.find('span.oe_pager_count').html(this.dataset.count); + do_update_pager: function(hide_index) { + var $pager = this.$element.find('#' + this.element_id + '_header div.oe_form_pager').eq(0); + var index = hide_index ? '-' : this.dataset.index + 1; + $pager.find('span.oe_pager_index').html(index); + $pager.find('span.oe_pager_count').html(this.dataset.count); }, do_onchange: function(widget, processed) { processed = processed || []; @@ -196,7 +209,8 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base self.on_record_loaded(result.result); }); }, - do_save: function() { + do_save: function(success) { + var self = this; if (!this.ready) { return false; } @@ -206,21 +220,31 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base f = this.fields[f]; if (f.invalid) { invalid = true; + f.update_dom(); } else if (f.touched) { values[f.name] = f.get_value(); } } if (invalid) { this.on_invalid(); + return false; } else { this.log("About to save", values); - this.dataset.write(this.datarecord.id, values, this.on_saved); + if (!this.datarecord.id) { + this.dataset.create(values, function(r) { + self.on_created(r, success); + }); + } else { + this.dataset.write(this.datarecord.id, values, function(r) { + self.on_saved(r, success); + }); + } + return true; } }, do_save_edit: function() { - if (this.do_save()) { - this.switch_readonly(); - } + this.do_save(); + //this.switch_readonly(); Use promises }, switch_readonly: function() { }, @@ -234,24 +258,48 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base } }); msg += "</ul>"; - this.notification.alert("The following fields are invalid :", msg); + this.notification.warn("The following fields are invalid :", msg); }, - on_saved: function(r) { + on_saved: function(r, success) { if (!r.result) { - this.log("Record was not saved"); + this.notification.warn("Record not saved", "Problem while saving record."); } else { - // Check response for exceptions, display error - this.notification['default']("Record saved", "The record #" + this.datarecord.id + " has been saved."); + this.notification.notify("Record saved", "The record #" + this.datarecord.id + " has been saved."); + if (success) { + success(r); + } + } + }, + on_created: function(r, success) { + if (!r.result) { + this.notification.warn("Record not created", "Problem while creating record."); + } else { + this.datarecord.id = arguments[0].result; + this.dataset.ids.push(this.datarecord.id); + this.dataset.index = this.dataset.ids.length - 1; + this.dataset.count++; + this.do_update_pager(); + this.notification.notify("Record created", "The record has been created with id #" + this.datarecord.id); + if (success) { + success(r); + } } }, do_search: function (domains, contexts, groupbys) { - this.notification['default']("Searching form"); + this.notification.notify("Searching form"); }, on_action: function (action) { - this.notification['default']('Executing action ' + action); + this.notification.notify('Executing action ' + action); }, do_cancel: function () { - this.notification['default']("Cancelling form"); + this.notification.notify("Cancelling form"); + }, + reload: function() { + if (this.datarecord.id) { + this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); + } else { + this.on_button_new(); + } } }); @@ -396,12 +444,12 @@ openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({ } } }, - handle_node: function(n) { - var type = this.view.fields_view.fields[n.attrs.name] || {}; - var widget_type = n.attrs.widget || type.type || n.tag; - var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, n); - if (n.tag == 'field' && n.attrs.nolabel != '1') { - var label = new (openerp.base.form.widgets.get_object('label')) (this.view, n); + handle_node: function(node) { + var type = this.view.fields_view.fields[node.attrs.name] || {}; + var widget_type = node.attrs.widget || type.type || node.tag; + var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, node); + if (node.tag == 'field' && node.attrs.nolabel != '1') { + var label = new (openerp.base.form.widgets.get_object('label')) (this.view, node); label["for"] = widget; this.add_widget(label); } @@ -450,6 +498,61 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({ init: function(view, node) { this._super(view, node); this.template = "WidgetButton"; + }, + start: function() { + this._super.apply(this, arguments); + this.$element.click(this.on_click); + }, + on_click: function(saved) { + var self = this; + if (saved !== true) { + this.view.do_save(function() { + self.on_click(true); + }); + } else { + if (this.node.attrs.confirm) { + var dialog = $('<div>' + this.node.attrs.confirm + '</div>').dialog({ + title: 'Confirm', + modal: true, + buttons: { + Ok: function() { + self.on_confirmed(); + $(this).dialog("close"); + }, + Cancel: function() { + $(this).dialog("close"); + } + } + }); + } else { + this.on_confirmed(); + } + } + }, + on_confirmed: function() { + var attrs = this.node.attrs; + if (attrs.special) { + return this.log("Should close the popup"); + } else { + var type = attrs.type || 'workflow'; + var context = _.extend({}, this.view.dataset.context, attrs.context || {}); + switch(type) { + case 'object': + return this.view.dataset.call(attrs.name, [this.view.datarecord.id], [context], this.on_button_object); + break; + default: + this.log(_.sprintf("Unsupported button type : %s", type)); + } + } + }, + on_button_object: function(r) { + if (r.result === false) { + this.log("Button object returns false"); + } else if (r.result.constructor == Object) { + console.log("TODO: send action to action manager", r.result); + } else { + this.view.reload(); + } } }); @@ -485,12 +588,13 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({ if (node.attrs.nolabel != '1' && this.colspan > 1) { this.colspan--; } - this.field = view.fields_view.fields[node.attrs.name]; + this.field = view.fields_view.fields[node.attrs.name] || {}; this.string = node.attrs.string || this.field.string; this.help = node.attrs.help || this.field.help; - this.nolabel = (node.attrs.nolabel == '1'); - this.readonly = (node.attrs.readonly == '1'); - this.required = (node.attrs.required == '1'); + this.invisible = (this.invisible || this.field.invisible == '1'); + this.nolabel = (this.field.nolabel || node.attrs.nolabel) == '1'; + this.readonly = (this.field.readonly || node.attrs.readonly) == '1'; + this.required = (this.field.required || node.attrs.required) == '1'; this.invalid = false; this.touched = false; }, @@ -499,6 +603,9 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({ this.invalid = false; this.update_dom(); }, + set_value_from_ui: function() { + this.value = undefined; + }, get_value: function() { return this.value; }, @@ -506,10 +613,17 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({ this._super.apply(this, arguments); this.$element.toggleClass('disabled', this.readonly); this.$element.toggleClass('required', this.required); - this.$element.toggleClass('invalid', this.invalid); + if (this.view.show_invalid) { + this.$element.toggleClass('invalid', this.invalid); + } }, on_ui_change: function() { this.touched = true; + this.set_value_from_ui(); + this.validate(); + this.view.on_form_changed(this); + }, + validate: function() { } }); @@ -531,31 +645,41 @@ openerp.base.form.FieldChar = openerp.base.form.Field.extend({ this._super.apply(this, arguments); this.$element.find('input').attr('disabled', this.readonly); }, - on_ui_change: function() { - this._super.apply(this, arguments); + set_value_from_ui: function() { this.value = this.$element.find('input').val(); - this.invalid = this.required && this.value == ""; - this.view.on_form_changed(this); + }, + validate: function() { + this.invalid = false; + if (this.value === false || this.value === "") { + this.invalid = this.required; + } else if (this.validation_regex) { + this.invalid = !this.validation_regex.test(this.value); + } } }); openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({ + init: function(view, node) { + this._super(view, node); + this.validation_regex = /@/; + } }); openerp.base.form.FieldUrl = openerp.base.form.FieldChar.extend({ }); openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({ + init: function(view, node) { + this._super(view, node); + this.validation_regex = /^\d+(\.\d+)?$/; + }, set_value: function(value) { this._super.apply(this, arguments); var show_value = (value != null && value !== false) ? value.toFixed(2) : ''; - this.$element.find('input').val(value); + this.$element.find('input').val(show_value); }, - on_ui_change: function() { - this._super.apply(this, arguments); + set_value_from_ui: function() { this.value = this.$element.find('input').val().replace(/,/g, '.'); - this.invalid = (this.required && this.value == "") || !this.value.match(/^\d+(\.\d+)?$/) ; - this.view.on_form_changed(this); } }); @@ -563,23 +687,13 @@ openerp.base.form.FieldDate = openerp.base.form.FieldChar.extend({ init: function(view, node) { this._super(view, node); this.template = "FieldDate"; + this.validation_regex = /^\d+-\d+-\d+$/; }, start: function() { this._super.apply(this, arguments); this.$element.find('input').change(this.on_ui_change).datepicker({ dateFormat: 'yy-mm-dd' }); - }, - set_value: function(value) { - this._super.apply(this, arguments); - var show_value = (value != null && value !== false) ? value : ''; - this.$element.find('input').val(show_value); - }, - on_ui_change: function() { - this._super.apply(this, arguments); - this.value = this.$element.find('input').val(); - this.invalid = this.required && this.value == ""; - this.view.on_form_changed(this); } }); @@ -587,6 +701,7 @@ openerp.base.form.FieldDatetime = openerp.base.form.FieldChar.extend({ init: function(view, node) { this._super(view, node); this.template = "FieldDatetime"; + this.validation_regex = /^\d+-\d+-\d+( \d+:\d+(:\d+)?)?$/; }, start: function() { this._super.apply(this, arguments); @@ -594,17 +709,6 @@ openerp.base.form.FieldDatetime = openerp.base.form.FieldChar.extend({ dateFormat: 'yy-mm-dd', timeFormat: 'hh:mm:ss' }); - }, - set_value: function(value) { - this._super.apply(this, arguments); - var show_value = (value != null && value !== false) ? value : ''; - this.$element.find('input').val(show_value); - }, - on_ui_change: function() { - this._super.apply(this, arguments); - this.value = this.$element.find('input').val(); - this.invalid = this.required && this.value == ""; - this.view.on_form_changed(this); } }); @@ -612,6 +716,7 @@ openerp.base.form.FieldText = openerp.base.form.Field.extend({ init: function(view, node) { this._super(view, node); this.template = "FieldText"; + this.validation_regex = null; }, start: function() { this._super.apply(this, arguments); @@ -626,11 +731,16 @@ openerp.base.form.FieldText = openerp.base.form.Field.extend({ this._super.apply(this, arguments); this.$element.find('textarea').attr('disabled', this.readonly); }, - on_ui_change: function() { - this._super.apply(this, arguments); + set_value_from_ui: function() { this.value = this.$element.find('textarea').val(); - this.invalid = this.required && this.value == ""; - this.view.on_form_changed(this); + }, + validate: function() { + this.invalid = false; + if (this.value === false || this.value === "") { + this.invalid = this.required; + } else if (this.validation_regex) { + this.invalid = !this.validation_regex.test(this.value); + } } }); @@ -652,15 +762,29 @@ openerp.base.form.FieldBoolean = openerp.base.form.Field.extend({ this._super.apply(this, arguments); this.$element.find('input')[0].checked = value; }, + set_value_from_ui: function() { + this.value = this.$element.find('input').is(':checked'); + }, update_dom: function() { this._super.apply(this, arguments); - this.$element.find('textarea').attr('disabled', this.readonly); + this.$element.find('input').attr('disabled', this.readonly); }, - on_ui_change: function() { - this._super.apply(this, arguments); - this.value = this.$element.find('input').is(':checked'); + validate: function() { this.invalid = this.required && !this.value; - this.view.on_form_changed(this); + } +}); + +openerp.base.form.FieldProgressBar = openerp.base.form.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldProgressBar"; + }, + start: function() { + this._super.apply(this, arguments); + this.$element.find('div').progressbar({ + value: this.value, + disabled: this.readonly + }); } }); @@ -685,15 +809,15 @@ openerp.base.form.FieldSelection = openerp.base.form.Field.extend({ this.$element.find('select')[0].selectedIndex = 0; } }, + set_value_from_ui: function() { + this.value = this.$element.find('select').val(); + }, update_dom: function() { this._super.apply(this, arguments); this.$element.find('select').attr('disabled', this.readonly); }, - on_ui_change: function() { - this._super.apply(this, arguments); - this.value = this.$element.find('select').val(); - this.invalid = this.required && this.value == ""; - this.view.on_form_changed(this); + validate: function() { + this.invalid = this.required && this.value === ""; } }); @@ -720,7 +844,7 @@ openerp.base.form.FieldOne2ManyDatasSet = openerp.base.DataSetStatic.extend({ this._super(id, data, callback); }, unlink: function() { - this.notification['default']('Unlinking o2m ' + this.ids); + this.notification.notify('Unlinking o2m ' + this.ids); } }); @@ -744,12 +868,14 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ this.viewmanager = new openerp.base.form.FieldOne2ManyViewManager(this.view.session, this.element_id, this.dataset, views); this.viewmanager.start(); }, - set_value_CASSEEEEEEEEEEEEEEEEE: function(value) { + set_value: function(value) { this.value = value; - this.log("o2m.set_value",value); - this.viewmanager.dataset.ids = value; - this.viewmanager.dataset.ids.count = value.length; - this.viewmanager.views.list.controller.do_update(); + if (value != false) { + this.log("o2m.set_value",value); + this.viewmanager.dataset.ids = value; + this.viewmanager.dataset.count = value.length; + this.viewmanager.views.list.controller.do_update(); + } }, get_value: function(value) { return this.operations; @@ -790,6 +916,7 @@ openerp.base.form.widgets = new openerp.base.Registry({ 'email' : 'openerp.base.form.FieldEmail', 'url' : 'openerp.base.form.FieldUrl', 'text' : 'openerp.base.form.FieldText', + 'text_wiki' : 'openerp.base.form.FieldText', 'date' : 'openerp.base.form.FieldDate', 'datetime' : 'openerp.base.form.FieldDatetime', 'selection' : 'openerp.base.form.FieldSelection', @@ -801,7 +928,7 @@ openerp.base.form.widgets = new openerp.base.Registry({ 'boolean' : 'openerp.base.form.FieldBoolean', 'float' : 'openerp.base.form.FieldFloat', 'integer': 'openerp.base.form.FieldFloat', - 'progressbar': 'openerp.base.form.FieldFloat', + 'progressbar': 'openerp.base.form.FieldProgressBar', 'float_time': 'openerp.base.form.FieldFloat' }); diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 8d4a1cce360..0b63639cb5d 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -245,8 +245,8 @@ openerp.base.ListView = openerp.base.Controller.extend( * The default implementation is to switch to a new record on the form view */ do_add_record: function () { - this.notification['default']('Add', "New record"); - // this.switch_to_record(null); + this.notification.notify('Add', "New record"); + this.switch_to_record(null); }, /** * Handles deletion of all selected lines diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js index ef10992d75f..21d1408f087 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -215,7 +215,7 @@ openerp.base.SearchView = openerp.base.Controller.extend({ * @param {Array} errors a never-empty array of error objects */ on_invalid: function (errors) { - this.notification['default']("Invalid Search", "triggered from search view"); + this.notification.notify("Invalid Search", "triggered from search view"); }, do_clear: function (e) { if (e && e.preventDefault) { e.preventDefault(); } diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index 5e6e3d72745..8cbc766df4d 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -229,10 +229,10 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({ set_toolbar: function(toolbar) { this.sections = []; var self = this; - _.each(["print", "action", "relate"], function(type) { - if (toolbar[type].length == 0) + _.each([["print", "Reports"], ["action", "Actions"], ["relate", "Links"]], function(type) { + if (toolbar[type[0]].length == 0) return; - var section = {elements:toolbar[type]}; + var section = {elements:toolbar[type[0]], label:type[1]}; self.sections.push(section); }); this.refresh(); @@ -240,6 +240,11 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({ refresh: function() { this.$element.html(QWeb.render("ViewManager.sidebar.internal", _.extend({_:_}, this))); var self = this; + this.$element.find(".toggle-sidebar").click(function(e) { + self.$element.toggleClass('open-sidebar closed-sidebar'); + e.stopPropagation(); + e.preventDefault(); + }); this.$element.find("a").click(function(e) { var $this = jQuery(this); var i = $this.attr("data-i"); @@ -269,8 +274,9 @@ openerp.base.ExternalActionManager = openerp.base.Controller.extend({ viewmanager.start(); } else if (action.target == "current") { this.rpc("/base/session/save_session_action", {the_action:action}, function(key) { - var url = window.location.href; - //window.open(); + var url = window.location.protocol + "//" + window.location.host + + window.location.pathname + "?" + jQuery.param({s_action:""+key}); + window.open(url); }); } } diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index 7586f62eff7..67a7d716d26 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -40,8 +40,8 @@ </tr> <tr> <td colspan="2"> - <div id="oe_footer" class="footer"> - Powered by OpenERP. + <div id="oe_footer" class="oe_footer"> + <p class="oe_footer_powered">Powered by <a href="http://www.openerp.com">openerp.com</a>.</p> </div> </td> </tr> @@ -60,7 +60,8 @@ </t> <t t-name="Header"> <a href="/" class="company_logo_link"> - <img src="/base/static/src/img/logo.png" border="0" class="company_logo"/> + <img t-att-src="typeof kitten == 'undefined' ? '/base/static/src/img/logo.png' : + 'http://placekitten.com/180/46'" border="0" class="company_logo"/> </a> <h1 class="header_title" t-if="session.session_is_valid()"> <span class="company">$company</span> - (<span class="database">$database</span>)<br/> @@ -232,7 +233,7 @@ </tr> <t t-name="FormView"> <h2 class="oe_view_title"><t t-esc="view.fields_view.arch.attrs.string"/></h2> - <div class="oe_form_header"> + <div class="oe_form_header" t-att-id="view.element_id + '_header'"> <div class="oe_form_buttons"> <!--<button type="button" class="oe_form_button_save">Save</button>--> <button type="button" class="oe_form_button_save_edit">Save & Edit</button> @@ -292,11 +293,13 @@ </div> </t> <t t-name="WidgetLabel"> - <label t-att-for="widget.element_id + '_field'" t-att-title="widget.help" style="display: block; text-align: right;" + <label t-att-for="widget.element_id + '_field'" + t-att-class="'oe_form_label' + (widget.help ? '_help' : '')" + t-att-title="widget.help" t-att-ondblclick="'console.log(\'' + widget.element_id + '\', openerp.screen.' + widget.element_id + ')'"> <t t-esc="widget.string"/> <span t-if="widget.help">?</span> - : + <t t-if="widget.string">:</t> </label> </t> <t t-name="FieldChar"> @@ -366,6 +369,9 @@ t-att-id="widget.element_id + '_field'" t-att-class="'field_' + widget.type"/> </t> +<t t-name="FieldProgressBar"> + <div t-opentag="true"></div> +</t> <t t-name="WidgetButton"> <button type="button" t-att-id="widget.element_id + '_button'" @@ -528,27 +534,33 @@ <input t-att-id="element_id"/> </t> <t t-name="ViewManager.sidebar"> - <div t-att-id="element_id" class="sidebar-main-div"> + <div t-att-id="element_id" class="sidebar-main-div open-sidebar"> </div> </t> <t t-name="ViewManager.sidebar.internal"> - <t t-if="sections.length > 0 && _.detect(sections, function(x) {return x.elements.length > 0;}) != undefined"> - <div t-att-id="element_id" class="sidebar-sub-div"> - <t t-set="i" t-value="0"/> - <t t-foreach="sections" t-as="section"> - <t t-if="section.elements.length > 0"> - <ul> - <t t-set="j" t-value="0"/> - <t t-foreach="section.elements" t-as="element"> - <li><a t-att-data-i="i" t-att-data-j="j" href="#"><t t-esc="element.name"/></a></li> - <t t-set="j" t-value="j+1"/> - </t> - </ul> - </t> - <t t-set="i" t-value="i+1"/> - </t> - </div> + <t t-set="the_condition" t-value="sections.length > 0 && _.detect(sections, function(x) + {return x.elements.length > 0;}) != undefined"/> + <t t-if="the_condition"> + <a class="toggle-sidebar"></a> + <div t-att-id="element_id" class="sidebar-sub-div"> + <div class="sidebar-displaying-div"> + <t t-set="i" t-value="0"/> + <t t-foreach="sections" t-as="section"> + <t t-if="section.elements.length > 0"> + <h2><t t-esc="section.label"/></h2> + <ul> + <t t-set="j" t-value="0"/> + <t t-foreach="section.elements" t-as="element"> + <li><a t-att-data-i="i" t-att-data-j="j" href="#"><t t-esc="element.name"/></a></li> + <t t-set="j" t-value="j+1"/> + </t> + </ul> + </t> + <t t-set="i" t-value="i+1"/> + </t> + </div> + </div> </t> </t> <t t-name="DialogWarning"> |
