summaryrefslogtreecommitdiff
path: root/addons/base
diff options
context:
space:
mode:
authorXavier Morel <xmo@openerp.com>2011-04-12 15:21:20 +0200
committerXavier Morel <xmo@openerp.com>2011-04-12 15:21:20 +0200
commitd7da740264e35dd67a7a891770b47d2bc38578ef (patch)
tree3c381edfd569b045e959ccd71dbe21dfef141421 /addons/base
parentb840e25c1d901f0d83c41eef38ada73e8d61bd7b (diff)
parentb70b02bc5138d7f5f4b70433cb47e28c39f1f953 (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.py6
-rw-r--r--addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.js1137
-rw-r--r--addons/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.min.js18
-rw-r--r--addons/base/static/lib/qweb/qweb.js4
-rw-r--r--addons/base/static/src/base.html1
-rw-r--r--addons/base/static/src/css/base.css181
-rw-r--r--addons/base/static/src/img/sideheader-a-bg.pngbin0 -> 126 bytes
-rw-r--r--addons/base/static/src/img/toggle-a-bg.pngbin0 -> 412 bytes
-rw-r--r--addons/base/static/src/js/chrome.js29
-rw-r--r--addons/base/static/src/js/data.js9
-rw-r--r--addons/base/static/src/js/form.js313
-rw-r--r--addons/base/static/src/js/list.js4
-rw-r--r--addons/base/static/src/js/search.js2
-rw-r--r--addons/base/static/src/js/views.js16
-rw-r--r--addons/base/static/src/xml/base.xml58
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
new file mode 100644
index 00000000000..ca9b64b8b01
--- /dev/null
+++ b/addons/base/static/src/img/sideheader-a-bg.png
Binary files differ
diff --git a/addons/base/static/src/img/toggle-a-bg.png b/addons/base/static/src/img/toggle-a-bg.png
new file mode 100644
index 00000000000..b9837ab233e
--- /dev/null
+++ b/addons/base/static/src/img/toggle-a-bg.png
Binary files differ
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 &amp; 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 &gt; 0 &amp;&amp; _.detect(sections, function(x) {return x.elements.length &gt; 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 &gt; 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 &gt; 0 &amp;&amp; _.detect(sections, function(x)
+ {return x.elements.length &gt; 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 &gt; 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">