summaryrefslogtreecommitdiff
path: root/addons/base/static/src
diff options
context:
space:
mode:
authorFabien Meghazi <fme@openerp.com>2011-08-01 11:26:17 +0200
committerFabien Meghazi <fme@openerp.com>2011-08-01 11:26:17 +0200
commita291c46b27bedda79273331ca601f528bf8d0bd2 (patch)
treec64f50b40269dca17c6d3997d6c5c7c116743fd5 /addons/base/static/src
parent26c20c4f23724563bc56021fd661671ad35421f6 (diff)
parentc8e7b43acbc3cd5c1dc40ceffe567b12a072cf40 (diff)
[MERGE] Merged with trunk
bzr revid: fme@openerp.com-20110801092617-f4vcc8dhx31n5ot3
Diffstat (limited to 'addons/base/static/src')
-rw-r--r--addons/base/static/src/base.html55
-rw-r--r--addons/base/static/src/css/base.css70
-rw-r--r--addons/base/static/src/css/data_export.css85
-rw-r--r--addons/base/static/src/img/collapse.gifbin0 -> 80 bytes
-rw-r--r--addons/base/static/src/img/expand.gifbin0 -> 81 bytes
-rw-r--r--addons/base/static/src/img/header.gifbin0 -> 182 bytes
-rw-r--r--addons/base/static/src/js/boot.js (renamed from addons/base/static/src/js/base.js)88
-rw-r--r--addons/base/static/src/js/chrome.js733
-rw-r--r--addons/base/static/src/js/core.js574
-rw-r--r--addons/base/static/src/js/data.js197
-rw-r--r--addons/base/static/src/js/data_export.js402
-rw-r--r--addons/base/static/src/js/form.js848
-rw-r--r--addons/base/static/src/js/list-editable.js172
-rw-r--r--addons/base/static/src/js/list.js515
-rw-r--r--addons/base/static/src/js/search.js358
-rw-r--r--addons/base/static/src/js/view_help.js15
-rw-r--r--addons/base/static/src/js/view_tree.js (renamed from addons/base/static/src/js/tree.js)4
-rw-r--r--addons/base/static/src/js/views.js395
-rw-r--r--addons/base/static/src/xml/base.xml398
19 files changed, 3248 insertions, 1661 deletions
diff --git a/addons/base/static/src/base.html b/addons/base/static/src/base.html
deleted file mode 100644
index 2761638c6d0..00000000000
--- a/addons/base/static/src/base.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<!DOCTYPE html>
-<html style="height: 100%">
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <title>OpenERP</title>
- <link rel="shortcut icon" href="/base/static/src/img/favicon.ico" type="image/x-icon"/>
-
- <script type="text/javascript" src="/base/static/lib/LABjs/LAB.js"></script>
- <script type="text/javascript" src="/base/static/lib/underscore/underscore.js"></script>
- <script type="text/javascript" src="/base/static/lib/underscore/underscore.string.js"></script>
- <script type="text/javascript" src="/base/static/lib/qweb/qweb2.js"></script>
- <!-- Uncomment in order to use legacy QWeb
- <script type="text/javascript" src="/base/static/lib/qweb/qweb.js"></script>
- -->
- <script type="text/javascript" src="/base/static/lib/jquery/jquery-1.5.2.js"></script>
- <script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js"></script>
- <script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-timepicker-addon.js"></script>
- <script type="text/javascript" src="/base/static/lib/jquery.ui.notify/js/jquery.notify.js"></script>
- <script type="text/javascript" src="/base/static/lib/jquery.superfish/js/hoverIntent.js"></script>
- <script type="text/javascript" src="/base/static/lib/jquery.superfish/js/superfish.js"></script>
- <script type="text/javascript" src="/base/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
- <script type="text/javascript" src="/base/static/lib/jquery.contextmenu/jquery.contextmenu.r2.packed.js"></script>
- <script type="text/javascript" src="/base/static/lib/datejs/date-en-US.js"></script>
- <script type="text/javascript" src="/base/static/lib/json/json2.js"></script>
- <script type="text/javascript" src="/base/static/src/js/base.js"></script>
- <script type="text/javascript" src="/base/static/src/js/dates.js"></script>
- <script type="text/javascript" src="/base/static/src/js/chrome.js"></script>
- <script type="text/javascript" src="/base/static/src/js/data.js"></script>
- <script type="text/javascript" src="/base/static/src/js/views.js"></script>
- <script type="text/javascript" src="/base/static/src/js/form.js"></script>
- <script type="text/javascript" src="/base/static/src/js/list.js"></script>
- <script type="text/javascript" src="/base/static/src/js/list-editable.js"></script>
- <script type="text/javascript" src="/base/static/src/js/tree.js"></script>
- <script type="text/javascript" src="/base/static/src/js/search.js"></script>
-
- <link rel="stylesheet" type="text/css" media="screen" href="/base/static/lib/jquery.ui/css/smoothness/jquery-ui-1.8.9.custom.css" />
- <link rel="stylesheet" type="text/css" media="screen" href="/base/static/lib/jquery.ui.notify/css/ui.notify.css" />
- <link rel="stylesheet" type="text/css" href="/base/static/lib/jquery.superfish/css/superfish.css" media="screen">
-
- <link rel="stylesheet" href="/base/static/src/css/base.css" type="text/css"/>
- <!--[if lte IE 7]>
- <link rel="stylesheet" href="/base/static/src/css/base-ie7.css" type="text/css"/>
- <![endif]-->
-
- <script type="text/javascript">
- $(function() {
- QWeb = window.QWeb || new QWeb2.Engine();
- var oe = openerp.init();
- oe.base.webclient("oe");
- });
- </script>
-</head>
-<body id="oe" class="openerp">
-</body>
-</html>
diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css
index 84b51b2cd54..b173386e234 100644
--- a/addons/base/static/src/css/base.css
+++ b/addons/base/static/src/css/base.css
@@ -30,6 +30,9 @@ body.openerp {
.openerp .oe-number {
text-align: right !important;
}
+.openerp .oe_hide {
+ display: none !important;
+}
/* STATES */
.openerp .on_logged {
@@ -577,6 +580,10 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
display: none;
}
+.openerp .searchview_extended_delete_prop {
+ text-decoration: none;
+}
+
.openerp .searchview_extended_delete_group span,
.openerp .searchview_extended_delete_prop span {
font-size: 0.9em;
@@ -591,6 +598,10 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
border: 1px solid silver;
}
+.openerp .oe-listview thead table {
+ width: 100%;
+ border: none;
+}
.openerp .oe-listview tr.odd {
background-color: #f3f3f3;
}
@@ -600,16 +611,27 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
.openerp .oe-listview tbody tr:hover {
background-color: #eae9f0;
}
+.openerp .oe-listview thead table tr,
+.openerp .oe-listview thead table tr:hover {
+ background: none;
+}
.openerp .oe-listview td,
.openerp .oe-listview th {
vertical-align: middle;
text-align: left;
}
-.openerp .oe-listview th.oe-sortable,
-.openerp .oe-listview th.oe-sortable .ui-icon {
+.openerp .oe-listview td.oe-record-delete {
+ text-align: right;
+}
+.openerp .oe-listview th.oe-sortable {
cursor: pointer;
}
+.openerp .oe-listview th.oe-sortable .ui-icon {
+ height: 1em;
+ display: inline;
+ display: inline-block;
+}
.openerp .oe-listview .oe-field-cell {
cursor: pointer;
@@ -632,9 +654,22 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
.openerp .oe-listview th.oe-actions {
text-align: left;
+ white-space: nowrap;
}
.openerp .oe-listview th.oe-list-pager {
text-align: right;
+ white-space: nowrap;
+}
+.openerp .oe-list-pager .oe-pager-state {
+ cursor: pointer;
+}
+
+.openerp .oe-listview .oe-group-name {
+ padding-right: 1em;
+}
+.openerp .oe-listview .oe-group-name,
+.openerp .oe-listview .oe-group-pagination {
+ white-space: nowrap;
}
.openerp .oe-listview tfoot td {
@@ -653,29 +688,29 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
rounded corners are a pain on tables: need to round not only table, but
also on the first and last children of the first and last row
*/
-.openerp .oe-listview table {
+.openerp .oe-listview table.oe-listview-content {
-webkit-border-radius: 7px;
-moz-border-radius: 7px;
border-radius: 7px;
}
-.openerp .oe-listview table thead tr:first-child th:first-child {
+.openerp .oe-listview table.oe-listview-content thead tr:first-child th:first-child {
-webkit-border-top-left-radius: 7px;
-moz-border-radius-topleft: 7px;
border-top-left-radius: 7px;
}
-.openerp .oe-listview table thead tr:first-child th:last-child {
+.openerp .oe-listview table.oe-listview-content thead tr:first-child th:last-child {
-webkit-border-top-right-radius: 7px;
-moz-border-radius-topright: 7px;
border-top-right-radius: 7px;
}
-.openerp .oe-listview table tfoot td:first-child,
-.openerp .oe-listview table tbody:last-child tr:last-child th:first-child {
+.openerp .oe-listview table.oe-listview-content tfoot td:first-child,
+.openerp .oe-listview table.oe-listview-content tbody:last-child tr:last-child th:first-child {
-webkit-border-bottom-left-radius: 7px;
-moz-border-radius-bottomleft: 7px;
border-bottom-left-radius: 7px;
}
-.openerp .oe-listview table tfoot td:last-child,
-.openerp .oe-listview table tbody:last-child tr:last-child td:last-child {
+.openerp .oe-listview table.oe-listview-content tfoot td:last-child,
+.openerp .oe-listview table.oe-listview-content tbody:last-child tr:last-child td:last-child {
-webkit-border-bottom-right-radius: 7px;
-moz-border-radius-bottomright: 7px;
border-bottom-right-radius: 7px;
@@ -773,13 +808,14 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
position: relative;
vertical-align: top;
}
-.openerp input.field_date, .openerp input.field_datetime {
- background: #fff url('../img/ui/field_calendar.png') no-repeat right center;
- background-origin: content-box;
- -moz-background-origin: content;
- -moz-background-origin: content-box;
- -webkit-background-origin: content-box;
+.openerp img.ui-datepicker-trigger, .openerp img.ui-datepicker-trigger {
+ margin-left: -20px;
+ vertical-align: middle;
+ cursor: pointer;
+ position: relative;
+ top: -1px;
}
+
/* http://www.quirksmode.org/dom/inputfile.html
* http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image
*/
@@ -987,6 +1023,10 @@ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */
.openerp .closed-sidebar .toggle-sidebar {
border-left: none;
}
+.openerp li.oe_sidebar_print {
+ padding-left: 20px;
+ background: 1px 3px url(../img/icons/gtk-print.png) no-repeat;
+}
.openerp.kitten-mode-activated .main_table {
background: url(http://placekitten.com/g/1500/800) repeat;
diff --git a/addons/base/static/src/css/data_export.css b/addons/base/static/src/css/data_export.css
new file mode 100644
index 00000000000..7b4b9707b39
--- /dev/null
+++ b/addons/base/static/src/css/data_export.css
@@ -0,0 +1,85 @@
+.openerp .oe_export_row tr{
+ background-color: #FFFFFF;
+ font-size: 0.8em;
+ height: 22px;
+}
+
+.openerp tr.ui-selected td {
+ background-color: #CCCCCC;
+}
+
+.openerp .oe_export_requiredfield {
+ background-color: #D2D2FF;
+}
+
+.openerp .oe_export_readonlyfield{
+ background-color: #999999;
+}
+
+.openerp .oe_export_row:hover{
+ background-color: #F3F3F3;
+}
+
+.openerp .oe_export_fields_selector_export {
+ width: 100%;
+ height: 400px;
+}
+
+.openerp .oe_export_fields_selector_left {
+ width: 50%;
+}
+
+.openerp div#left_field_panel {
+ overflow: scroll;
+ width: 100%;
+ height: 400px;
+ border: solid #999999 1px;
+}
+
+.openerp .oe_export_fields_selector_center {
+ width: 102px;
+}
+
+.openerp .oe_export_fields_selector_right {
+ width: 45%;
+ height: 400px;
+}
+
+.openerp .oe_export_fields_selector_export select{
+ width: 100%;
+ height: 100%;
+}
+
+.openerp .oe_export_tree_header{
+ border: 0.5px solid #E3E3E3;
+ text-align: left;
+ white-space: nowrap;
+ padding: 4px 5px;
+ background: url(/base/static/src/img/header.gif);
+}
+
+
+.openerp table.tree_grid{
+ border: 1px solid #E3E3E3;
+ text-align: left;
+ white-space: nowrap;
+ background-color:#E3E3E3;
+ border-collapse: collapse;
+ width: 100%;
+}
+
+.openerp table.tree_grid a:hover {
+ color: blue;
+ border: none;
+}
+
+.openerp table.tree_grid a {
+ color: #5F5C5C;
+ border: none;
+ display: block;
+}
+
+.openerp .oe_export_button_export {
+ border: 1px solid #006;
+ background-color: #F3F3F3;
+}
diff --git a/addons/base/static/src/img/collapse.gif b/addons/base/static/src/img/collapse.gif
new file mode 100644
index 00000000000..759e72ebcfe
--- /dev/null
+++ b/addons/base/static/src/img/collapse.gif
Binary files differ
diff --git a/addons/base/static/src/img/expand.gif b/addons/base/static/src/img/expand.gif
new file mode 100644
index 00000000000..ca98d0b5f2b
--- /dev/null
+++ b/addons/base/static/src/img/expand.gif
Binary files differ
diff --git a/addons/base/static/src/img/header.gif b/addons/base/static/src/img/header.gif
new file mode 100644
index 00000000000..f40bd9c0f6a
--- /dev/null
+++ b/addons/base/static/src/img/header.gif
Binary files differ
diff --git a/addons/base/static/src/js/base.js b/addons/base/static/src/js/boot.js
index 55e4929e4c0..c16f2d261f5 100644
--- a/addons/base/static/src/js/base.js
+++ b/addons/base/static/src/js/boot.js
@@ -1,71 +1,5 @@
-/*---------------------------------------------------------
- * John Resig Class, to be moved to openerp.base.Class
- *---------------------------------------------------------*/
-
-(function() {
- var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
- // The base Class implementation (does nothing)
- this.Class = function(){};
-
- // Create a new Class that inherits from this class
- Class.extend = function(prop) {
- var _super = this.prototype;
-
- // Instantiate a base class (but only create the instance,
- // don't run the init constructor)
- initializing = true;
- var prototype = new this();
- initializing = false;
-
- // Copy the properties over onto the new prototype
- for (var name in prop) {
- // Check if we're overwriting an existing function
- prototype[name] = typeof prop[name] == "function" &&
- typeof _super[name] == "function" && fnTest.test(prop[name]) ?
- (function(name, fn){
- return function() {
- var tmp = this._super;
-
- // Add a new ._super() method that is the same method
- // but on the super-class
- this._super = _super[name];
-
- // The method only need to be bound temporarily, so we
- // remove it when we're done executing
- var ret = fn.apply(this, arguments);
- this._super = tmp;
-
- return ret;
- };
- })(name, prop[name]) :
- prop[name];
- }
-
- // The dummy class constructor
- function Class() {
- // All construction is actually done in the init method
- if ( !initializing && this.init ) {
- var ret = this.init.apply(this, arguments);
- if (ret) { return ret; }
- }
- return this;
- }
-
- // Populate our constructed prototype object
- Class.prototype = prototype;
-
- // Enforce the constructor to be what we expect
- Class.constructor = Class;
-
- // And make this class extendable
- Class.extend = arguments.callee;
-
- return Class;
- };
-})();
-
//---------------------------------------------------------
-// OpenERP initialisation and black magic about the pool
+// OpenERP Web Boostrap
//---------------------------------------------------------
/**
@@ -107,11 +41,13 @@
_session_id: "session" + session_counter++,
screen: openerp.screen,
sessions: openerp.sessions,
- base: {}
+ base: {},
+ web_mobile: {}
};
openerp.sessions[new_instance._session_id] = new_instance;
- if (!skip_init)
+ if (!skip_init){
openerp.base(new_instance);
+ }
return new_instance;
}
};
@@ -121,8 +57,8 @@
// OpenERP base module split
//---------------------------------------------------------
-/** @namespace */
openerp.base = function(instance) {
+ openerp.base.core(instance);
openerp.base.dates(instance);
openerp.base.chrome(instance);
openerp.base.data(instance);
@@ -135,9 +71,6 @@ openerp.base = function(instance) {
if (openerp.base.list) {
openerp.base.list(instance);
}
- if (openerp.base.tree) {
- openerp.base.tree(instance);
- }
if (openerp.base. m2o) {
openerp.base.m2o(instance);
}
@@ -147,6 +80,15 @@ openerp.base = function(instance) {
if (openerp.base.list && openerp.base.list.editable) {
openerp.base.list.editable(instance);
}
+ if (openerp.web_mobile) {
+ openerp.web_mobile(instance);
+ }
+ if (openerp.base.view_tree) {
+ openerp.base.view_tree(instance);
+ }
+ if (openerp.base.data_export) {
+ openerp.base.data_export(instance);
+ }
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js
index 5e1c7432cb8..ed84c32071b 100644
--- a/addons/base/static/src/js/chrome.js
+++ b/addons/base/static/src/js/chrome.js
@@ -4,325 +4,15 @@
openerp.base.chrome = function(openerp) {
-openerp.base.callback = function(obj, method) {
- var callback = function() {
- var args = Array.prototype.slice.call(arguments);
- var r;
- for(var i = 0; i < callback.callback_chain.length; i++) {
- var c = callback.callback_chain[i];
- if(c.unique) {
- callback.callback_chain.splice(i, 1);
- i -= 1;
- }
- r = c.callback.apply(c.self, c.args.concat(args));
- // TODO special value to stop the chain
- // openerp.base.callback_stop
- }
- return r;
- };
- callback.callback_chain = [];
- callback.add = function(f) {
- if(typeof(f) == 'function') {
- f = { callback: f, args: Array.prototype.slice.call(arguments, 1) };
- }
- f.self = f.self || null;
- f.args = f.args || [];
- f.unique = !!f.unique;
- if(f.position == 'last') {
- callback.callback_chain.push(f);
- } else {
- callback.callback_chain.unshift(f);
- }
- return callback;
- };
- callback.add_first = function(f) {
- return callback.add.apply(null,arguments);
- };
- callback.add_last = function(f) {
- return callback.add({
- callback: f,
- args: Array.prototype.slice.call(arguments, 1),
- position: "last"
- });
- };
-
- return callback.add({
- callback: method,
- self:obj,
- args:Array.prototype.slice.call(arguments, 2)
- });
-};
-
-/**
- * Base error for lookup failure
- *
- * @class
- */
-openerp.base.NotFound = Class.extend( /** @lends openerp.base.NotFound# */ {
-});
-openerp.base.KeyNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.KeyNotFound# */ {
- /**
- * Thrown when a key could not be found in a mapping
- *
- * @constructs
- * @extends openerp.base.NotFound
- * @param {String} key the key which could not be found
- */
- init: function (key) {
- this.key = key;
- },
- toString: function () {
- return "The key " + this.key + " was not found";
- }
-});
-openerp.base.ObjectNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.ObjectNotFound# */ {
+openerp.base.Session = openerp.base.Widget.extend( /** @lends openerp.base.Session# */{
/**
- * Thrown when an object path does not designate a valid class or object
- * in the openerp hierarchy.
- *
- * @constructs
- * @extends openerp.base.NotFound
- * @param {String} path the invalid object path
- */
- init: function (path) {
- this.path = path;
- },
- toString: function () {
- return "Could not find any object of path " + this.path;
- }
-});
-openerp.base.Registry = Class.extend( /** @lends openerp.base.Registry# */ {
- /**
- * Stores a mapping of arbitrary key (strings) to object paths (as strings
- * as well).
- *
- * Resolves those paths at query time in order to always fetch the correct
- * object, even if those objects have been overloaded/replaced after the
- * registry was created.
- *
- * An object path is simply a dotted name from the openerp root to the
- * object pointed to (e.g. ``"openerp.base.Session"`` for an OpenERP
- * session object).
- *
* @constructs
- * @param {Object} mapping a mapping of keys to object-paths
- */
- init: function (mapping) {
- this.map = mapping || {};
- },
- /**
- * Retrieves the object matching the provided key string.
- *
- * @param {String} key the key to fetch the object for
- * @returns {Class} the stored class, to initialize
- *
- * @throws {openerp.base.KeyNotFound} if the object was not in the mapping
- * @throws {openerp.base.ObjectNotFound} if the object path was invalid
- */
- get_object: function (key) {
- var path_string = this.map[key];
- if (path_string === undefined) {
- throw new openerp.base.KeyNotFound(key);
- }
-
- var object_match = openerp;
- var path = path_string.split('.');
- // ignore first section
- for(var i=1; i<path.length; ++i) {
- object_match = object_match[path[i]];
-
- if (object_match === undefined) {
- throw new openerp.base.ObjectNotFound(path_string);
- }
- }
- return object_match;
- },
- /**
- * Tries a number of keys, and returns the first object matching one of
- * the keys.
- *
- * @param {Array} keys a sequence of keys to fetch the object for
- * @returns {Class} the first class found matching an object
- *
- * @throws {openerp.base.KeyNotFound} if none of the keys was in the mapping
- * @trows {openerp.base.ObjectNotFound} if a found object path was invalid
- */
- get_any: function (keys) {
- for (var i=0; i<keys.length; ++i) {
- try {
- return this.get_object(keys[i]);
- } catch (e) {
- if (e instanceof openerp.base.KeyNotFound) {
- continue;
- }
- throw e;
- }
- }
- throw new openerp.base.KeyNotFound(keys.join(','));
- },
- /**
- * Adds a new key and value to the registry.
- *
- * This method can be chained.
- *
- * @param {String} key
- * @param {String} object_path fully qualified dotted object path
- * @returns {openerp.base.Registry} itself
- */
- add: function (key, object_path) {
- this.map[key] = object_path;
- return this;
- },
- /**
- * Creates and returns a copy of the current mapping, with the provided
- * mapping argument added in (replacing existing keys if needed)
- *
- * @param {Object} [mapping={}] a mapping of keys to object-paths
- */
- clone: function (mapping) {
- return new openerp.base.Registry(
- _.extend({}, this.map, mapping || {}));
- }
-});
-
-openerp.base.BasicController = Class.extend( /** @lends openerp.base.BasicController# */{
- /**
- * rpc operations, event binding and callback calling should be done in
- * start() instead of init so that event can be hooked in between.
- *
- * @constructs
- */
- init: function(element_id) {
- this.element_id = element_id;
- this.$element = $('#' + element_id);
- if (element_id) {
- openerp.screen[element_id] = this;
- }
-
- // Transform on_* method into openerp.base.callbacks
- for (var name in this) {
- if(typeof(this[name]) == "function") {
- this[name].debug_name = name;
- // bind ALL function to this not only on_and _do ?
- if((/^on_|^do_/).test(name)) {
- this[name] = openerp.base.callback(this, this[name]);
- }
- }
- }
- },
- /**
- * Controller start
- * event binding, rpc and callback calling required to initialize the
- * object can happen here
- *
- * Returns a promise object letting callers (subclasses and direct callers)
- * know when this component is done starting
- *
- * @returns {jQuery.Deferred}
- */
- start: function() {
- // returns an already fulfilled promise. Maybe we could return nothing?
- // $.when can take non-deferred and in that case it simply considers
- // them all as fulfilled promises.
- // But in thise case we *have* to ensure callers use $.when and don't
- // try to call deferred methods on this return value.
- return $.Deferred().done().promise();
- },
- stop: function() {
- },
- log: function() {
- var args = Array.prototype.slice.call(arguments);
- var caller = arguments.callee.caller;
- // TODO add support for line number using
- // https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
- // args.unshift("" + caller.debug_name);
- this.on_log.apply(this,args);
- },
- on_log: function() {
- if(window.openerp.debug || (window.location.search.indexOf('?debug') !== -1)) {
- var notify = false;
- var body = false;
- if(window.console) {
- console.log(arguments);
- } else {
- body = true;
- }
- var a = Array.prototype.slice.call(arguments, 0);
- for(var i = 0; i < a.length; i++) {
- var v = a[i]==null ? "null" : a[i].toString();
- if(i==0) {
- notify = v.match(/^not/);
- body = v.match(/^bod/);
- }
- if(body) {
- $('<pre></pre>').text(v).appendTo($('body'));
- }
- if(notify && this.notification) {
- this.notification.notify("Logging:",v);
- }
- }
- }
-
- }
-});
-
-/**
- * Generates an inherited class that replaces all the methods by null methods (methods
- * that does nothing and always return undefined).
- *
- * @param {Class} claz
- * @param {dict} add Additional functions to override.
- * @return {Class}
- */
-openerp.base.generate_null_object_class = function(claz, add) {
- var newer = {};
- var copy_proto = function(prototype) {
- for (var name in prototype) {
- if(typeof prototype[name] == "function") {
- newer[name] = function() {};
- }
- }
- if (prototype.prototype)
- copy_proto(prototype.prototype);
- };
- copy_proto(claz.prototype);
- newer.init = openerp.base.BasicController.prototype.init;
- var tmpclass = claz.extend(newer);
- return tmpclass.extend(add || {});
-};
-
-openerp.base.Notification = openerp.base.BasicController.extend({
- init: function(element_id) {
- this._super(element_id);
- this.$element.notify({
- speed: 500,
- expires: 1500
- });
- },
- notify: function(title, text) {
- this.$element.notify('create', {
- title: title,
- text: text
- });
- },
- warn: function(title, text) {
- this.$element.notify('create', 'oe_notification_alert', {
- title: title,
- text: text
- });
- }
-});
-
-openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.base.Session# */{
- /**
- * @constructs
- * @extends openerp.base.BasicController
* @param element_id to use for exception reporting
* @param server
* @param port
*/
- init: function(element_id, server, port) {
- this._super(element_id);
+ init: function(parent, element_id, server, port) {
+ this._super(parent, element_id);
this.server = (server == undefined) ? location.hostname : server;
this.port = (port == undefined) ? location.port : port;
this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp";
@@ -350,15 +40,12 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
* @param {Object} params call parameters
* @param {Function} success_callback function to execute on RPC call success
* @param {Function} error_callback function to execute on RPC call failure
- * one
* @returns {jQuery.Deferred} jquery-provided ajax deferred
*/
rpc: function(url, params, success_callback, error_callback) {
var self = this;
// Construct a JSON-RPC2 request, method is currently unused
params.session_id = this.session_id;
- // niv: wtf?
- //params.context = typeof(params.context) != "undefined" ? params.context : this.context;
// Call using the rpc_mode
var deferred = $.Deferred();
@@ -368,7 +55,7 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
params: params,
id:null
}).then(function () {deferred.resolve.apply(deferred, arguments);},
- function(error) {deferred.reject(error, $.Event());});
+ function(error) {deferred.reject(error, $.Event());});
return deferred.fail(function() {
deferred.fail(function(error, event) {
if (!event.isDefaultPrevented()) {
@@ -401,30 +88,35 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
}, url);
var deferred = $.Deferred();
$.ajax(ajax).done(function(response, textStatus, jqXHR) {
- self.on_rpc_response();
- if (response.error) {
- if (response.error.data.type == "session_invalid") {
- self.uid = false;
- self.on_session_invalid(function() {
- self.rpc(url, payload.params,
- function() {deferred.resolve.apply(deferred, arguments);},
- function() {deferred.reject.apply(deferred, arguments);});
- });
- } else {
- deferred.reject(response.error);
- }
- } else {
- deferred.resolve(response["result"], textStatus, jqXHR);
- }
- }).fail(function(jqXHR, textStatus, errorThrown) {
- self.on_rpc_response();
- var error = {
- code: -32098,
- message: "XmlHttpRequestError " + errorThrown,
- data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] }
- };
- deferred.reject(error);
+ self.on_rpc_response();
+ if (!response.error) {
+ deferred.resolve(response["result"], textStatus, jqXHR);
+ return;
+ }
+ if (response.error.data.type !== "session_invalid") {
+ deferred.reject(response.error);
+ return;
+ }
+ self.uid = false;
+ self.on_session_invalid(function() {
+ self.rpc(url, payload.params,
+ function() {
+ deferred.resolve.apply(deferred, arguments);
+ },
+ function(error, event) {
+ event.preventDefault();
+ deferred.reject.apply(deferred, arguments);
+ });
});
+ }).fail(function(jqXHR, textStatus, errorThrown) {
+ self.on_rpc_response();
+ var error = {
+ code: -32098,
+ message: "XmlHttpRequestError " + errorThrown,
+ data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] }
+ };
+ deferred.reject(error);
+ });
return deferred.promise();
},
on_rpc_request: function() {
@@ -499,7 +191,7 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
for(var i=0; i<cookies.length; ++i) {
var cookie = cookies[i].replace(/^\s*/, '');
if(cookie.indexOf(nameEQ) === 0) {
- return decodeURIComponent(cookie.substring(nameEQ.length));
+ return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length)));
}
}
return null;
@@ -515,7 +207,7 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
set_cookie: function (name, value, ttl) {
ttl = ttl || 24*60*60*365;
document.cookie = [
- this.element_id + '|' + name + '=' + encodeURIComponent(value),
+ this.element_id + '|' + name + '=' + encodeURIComponent(JSON.stringify(value)),
'max-age=' + ttl,
'expires=' + new Date(new Date().getTime() + ttl*1000).toGMTString()
].join(';');
@@ -526,15 +218,20 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
load_modules: function() {
var self = this;
this.rpc('/base/session/modules', {}, function(result) {
- self.module_list = result['modules'];
+ self.module_list = result;
var modules = self.module_list.join(',');
- self.rpc('/base/session/csslist', {mods: modules}, self.do_load_css);
- self.rpc('/base/session/jslist', {"mods": modules}, self.debug ? self.do_load_modules_debug : self.do_load_modules_prod);
+ if(self.debug || true) {
+ self.rpc('/base/webclient/csslist', {"mods": modules}, self.do_load_css);
+ self.rpc('/base/webclient/jslist', {"mods": modules}, self.do_load_js);
+ } else {
+ self.do_load_css(["/base/webclient/css?mods="+modules]);
+ self.do_load_js(["/base/webclient/js?mods="+modules]);
+ }
openerp._modules_loaded = true;
});
},
- do_load_css: function (result) {
- _.each(result.files, function (file) {
+ do_load_css: function (files) {
+ _.each(files, function (file) {
$('head').append($('<link>', {
'href': file,
'rel': 'stylesheet',
@@ -542,16 +239,23 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
}));
});
},
- do_load_modules_debug: function(result) {
- $LAB.setOptions({AlwaysPreserveOrder: true})
- .script(result.files)
- .wait(this.on_modules_loaded);
- },
- do_load_modules_prod: function() {
- // load merged ones
- // /base/session/css?mod=mod1,mod2,mod3
- // /base/session/js?mod=mod1,mod2,mod3
- // use $.getScript(‘your_3rd_party-script.js’); ? i want to keep lineno !
+ do_load_js: function(files) {
+ var self = this;
+ if(files.length != 0) {
+ var file = files.shift();
+ var tag = document.createElement('script');
+ tag.type = 'text/javascript';
+ tag.src = file;
+ tag.onload = tag.onreadystatechange = function() {
+ if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done )
+ return;
+ tag.onload_done = true;
+ self.do_load_js(files);
+ };
+ document.head.appendChild(tag);
+ } else {
+ this.on_modules_loaded();
+ }
},
on_modules_loaded: function() {
for(var j=0; j<this.module_list.length; j++) {
@@ -568,209 +272,34 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
}
});
-// A controller takes an already existing element
-// new()
-// start()
-openerp.base.Controller = openerp.base.BasicController.extend( /** @lends openerp.base.Controller# */{
- /**
- * Controller manifest used to declare standard controller attributes
- */
- controller_manifest: {
- register: null,
- template: "",
- element_post_prefix: false
- },
- /**
- * Controller registry,
- */
- controller_registry: {
- },
- /**
- * Add a new child controller
- */
- controller_get: function(key) {
- return this.controller_registry[key];
- // OR should build it ? setting parent correctly ?
- // function construct(constructor, args) {
- // function F() {
- // return constructor.apply(this, args);
- // }
- // F.prototype = constructor.prototype;
- // return new F();
- // }
- // var obj = this.controller_registry[key];
- // if(obj) {
- // return construct(obj, Array.prototype.slice.call(arguments, 1));
- // }
- },
- controller_new: function(key) {
- var self;
- // OR should contrustct it ? setting parent correctly ?
- function construct(constructor, args) {
- function F() {
- return constructor.apply(this, args);
- }
- F.prototype = constructor.prototype;
- return new F();
- }
- var obj = this.controller_registry[key];
- if(obj) {
- // TODO Prepend parent
- return construct(obj, Array.prototype.slice.call(arguments, 1));
- }
- },
- /**
- * @constructs
- * @extends openerp.base.BasicController
- */
- init: function(parent_or_session, element_id) {
- this._super(element_id);
- this.controller_parent = null;
- this.controller_children = [];
- if(parent_or_session) {
- if(parent_or_session.session) {
- this.parent = parent_or_session;
- this.session = this.parent.session;
- if(this.parent.children) {
- this.parent.children.push(this);
- }
- } else {
- // TODO remove Backward compatilbility
- this.session = parent_or_session;
- }
- }
- // Apply manifest options
- if(this.controller_manifest) {
- var register = this.controller_manifest.register;
- // TODO accept a simple string
- if(register) {
- for(var i=0; i<register.length; i++) {
- this.controller_registry[register[i]] = this;
- }
- }
- // TODO if post prefix
- //this.element_id = _.uniqueId(_.toArray(arguments).join('_'));
- }
- },
- /**
- * Performs a JSON-RPC call
- *
- * @param {String} url endpoint url
- * @param {Object} data RPC parameters
- * @param {Function} success RPC call success callback
- * @param {Function} error RPC call error callback
- * @returns {jQuery.Deferred} deferred object for the RPC call
- */
- rpc: function(url, data, success, error) {
- // TODO: support additional arguments ?
- return this.session.rpc(url, data, success, error);
- }
-});
-
-// A widget is a controller that doesnt take an element_id
-// it render its own html that you should insert into the dom
-// and bind it a start()
-//
-// new()
-// render() and insert it place it where you want
-// start()
-openerp.base.BaseWidget = openerp.base.Controller.extend({
- /**
- * The name of the QWeb template that will be used for rendering. Must be
- * redefined in subclasses or the render() method can not be used.
- *
- * @type string
- */
- template: null,
- /**
- * The prefix used to generate an id automatically. Should be redefined in
- * subclasses. If it is not defined, a default identifier will be used.
- *
- * @type string
- */
- identifier_prefix: 'generic-identifier',
- /**
- * Base class for widgets. Handle rendering (based on a QWeb template),
- * identifier generation, parenting and destruction of the widget.
- * Also initialize the identifier.
- *
- * @constructs
- * @params {openerp.base.search.BaseWidget} parent The parent widget.
- */
- init: function (parent, session) {
- this._super(session);
- this.children = [];
- this.parent = null;
- this.set_parent(parent);
- this.make_id(this.identifier_prefix);
- },
- /**
- * Sets and returns a globally unique identifier for the widget.
- *
- * If a prefix is appended, the identifier will be appended to it.
- *
- * @params sections prefix sections, empty/falsy sections will be removed
- */
- make_id: function () {
- this.element_id = _.uniqueId(_.toArray(arguments).join('_'));
- return this.element_id;
- },
- /**
- * "Starts" the widgets. Called at the end of the rendering, this allows
- * to get a jQuery object referring to the DOM ($element attribute).
- */
- start: function () {
- this._super();
- var tmp = document.getElementById(this.element_id);
- this.$element = tmp ? $(tmp) : null;
- },
- /**
- * "Stops" the widgets. Called when the view destroys itself, this
- * lets the widgets clean up after themselves.
- */
- stop: function () {
- var tmp_children = this.children;
- this.children = [];
- _.each(tmp_children, function(x) {
- x.stop();
+openerp.base.Notification = openerp.base.Widget.extend({
+ init: function(parent, element_id) {
+ this._super(parent, element_id);
+ this.$element.notify({
+ speed: 500,
+ expires: 1500
});
- if(this.$element != null) {
- this.$element.remove();
- }
- this.set_parent(null);
- this._super();
},
- /**
- * Set the parent of this component, also un-register the previous parent
- * if there was one.
- *
- * @param {openerp.base.BaseWidget} parent The new parent.
- */
- set_parent: function(parent) {
- if(this.parent) {
- this.parent.children = _.without(this.parent.children, this);
- }
- this.parent = parent;
- if(this.parent) {
- parent.children.push(this);
- }
+ notify: function(title, text) {
+ this.$element.notify('create', {
+ title: title,
+ text: text
+ });
},
- /**
- * Render the widget. This.template must be defined.
- * The content of the current object is passed as context to the template.
- *
- * @param {object} additional Additional context arguments to pass to the template.
- */
- render: function (additional) {
- return QWeb.render(this.template, _.extend({}, this, additional != null ? additional : {}));
+ warn: function(title, text) {
+ this.$element.notify('create', 'oe_notification_alert', {
+ title: title,
+ text: text
+ });
}
});
-openerp.base.Dialog = openerp.base.BaseWidget.extend({
+openerp.base.Dialog = openerp.base.OldWidget.extend({
dialog_title: "",
identifier_prefix: 'dialog',
- init: function (session, options) {
- this._super(null, session);
+ init: function (parent, options) {
+ var self = this;
+ this._super(parent);
this.options = {
modal: true,
width: 'auto',
@@ -780,7 +309,10 @@ openerp.base.Dialog = openerp.base.BaseWidget.extend({
min_height: 0,
max_height: '100%',
autoOpen: false,
- buttons: {}
+ buttons: {},
+ beforeClose: function () {
+ self.on_close();
+ }
};
for (var f in this) {
if (f.substr(0, 10) == 'on_button_') {
@@ -843,18 +375,23 @@ openerp.base.Dialog = openerp.base.BaseWidget.extend({
this.set_options(options);
this.$dialog.dialog(this.options).dialog('open');
},
- close: function(options) {
+ close: function() {
+ // Closes the dialog but leave it in a state where it could be opened again.
this.$dialog.dialog('close');
},
+ on_close: function() {
+ },
stop: function () {
+ // Destroy widget
+ this.close();
this.$dialog.dialog('destroy');
}
});
openerp.base.CrashManager = openerp.base.Dialog.extend({
identifier_prefix: 'dialog_crash',
- init: function(session) {
- this._super(session);
+ init: function(parent) {
+ this._super(parent);
this.session.on_rpc_error.add(this.on_rpc_error);
},
on_button_Ok: function() {
@@ -887,12 +424,9 @@ openerp.base.CrashManager = openerp.base.Dialog.extend({
}
});
-openerp.base.Loading = openerp.base.Controller.extend({
- controller_manifest: {
- register: ["Loading"]
- },
- init: function(session, element_id) {
- this._super(session, element_id);
+openerp.base.Loading = openerp.base.Widget.extend({
+ init: function(parent, element_id) {
+ this._super(parent, element_id);
this.count = 0;
this.session.on_rpc_request.add_first(this.on_rpc_event, 1);
this.session.on_rpc_response.add_last(this.on_rpc_event, -1);
@@ -909,27 +443,29 @@ openerp.base.Loading = openerp.base.Controller.extend({
}
});
-openerp.base.Database = openerp.base.Controller.extend({
+openerp.base.Database = openerp.base.Widget.extend({
});
-openerp.base.Login = openerp.base.Controller.extend({
- init: function(session, element_id) {
- this._super(session, element_id);
+openerp.base.Login = openerp.base.Widget.extend({
+ remember_creditentials: true,
+ init: function(parent, element_id) {
+ this._super(parent, element_id);
this.has_local_storage = typeof(localStorage) != 'undefined';
this.selected_db = null;
this.selected_login = null;
- this.selected_password = null;
- this.remember = false;
- if (this.has_local_storage && localStorage.getItem('remember_creditentials') === 'true') {
- this.remember = true;
+ if (this.has_local_storage && this.remember_creditentials) {
this.selected_db = localStorage.getItem('last_db_login_success');
this.selected_login = localStorage.getItem('last_login_login_success');
- this.selected_password = localStorage.getItem('last_password_login_success');
+ }
+ if (jQuery.deparam(jQuery.param.querystring()).debug != undefined) {
+ this.selected_db = this.selected_db || "trunk";
+ this.selected_login = this.selected_login || "admin";
+ this.selected_password = this.selected_password || "a";
}
},
start: function() {
var self = this;
- this.rpc("/base/session/get_databases_list", {}, function(result) {
+ this.rpc("/base/database/get_databases_list", {}, function(result) {
self.db_list = result.db_list;
self.display();
}, function() {
@@ -953,22 +489,17 @@ openerp.base.Login = openerp.base.Controller.extend({
var db = $e.find("form [name=db]").val();
var login = $e.find("form input[name=login]").val();
var password = $e.find("form input[name=password]").val();
- var remember = $e.find("form input[name=remember]").attr('checked');
//$e.hide();
// Should hide then call callback
this.session.session_login(db, login, password, function() {
if(self.session.session_is_valid()) {
if (self.has_local_storage) {
- if(remember) {
- localStorage.setItem('remember_creditentials', 'true');
+ if(self.remember_creditentials) {
localStorage.setItem('last_db_login_success', db);
localStorage.setItem('last_login_login_success', login);
- localStorage.setItem('last_password_login_success', password);
} else {
- localStorage.setItem('remember_creditentials', '');
localStorage.setItem('last_db_login_success', '');
localStorage.setItem('last_login_login_success', '');
- localStorage.setItem('last_password_login_success', '');
}
}
self.on_login_valid();
@@ -993,9 +524,9 @@ openerp.base.Login = openerp.base.Controller.extend({
}
});
-openerp.base.Header = openerp.base.Controller.extend({
- init: function(session, element_id) {
- this._super(session, element_id);
+openerp.base.Header = openerp.base.Widget.extend({
+ init: function(parent, element_id) {
+ this._super(parent, element_id);
},
start: function() {
this.do_update();
@@ -1007,9 +538,9 @@ openerp.base.Header = openerp.base.Controller.extend({
on_logout: function() {}
});
-openerp.base.Menu = openerp.base.Controller.extend({
- init: function(session, element_id, secondary_menu_id) {
- this._super(session, element_id);
+openerp.base.Menu = openerp.base.Widget.extend({
+ init: function(parent, element_id, secondary_menu_id) {
+ this._super(parent, element_id);
this.secondary_menu_id = secondary_menu_id;
this.$secondary_menu = $("#" + secondary_menu_id);
this.menu = false;
@@ -1088,45 +619,43 @@ openerp.base.Menu = openerp.base.Controller.extend({
}
});
-openerp.base.Homepage = openerp.base.Controller.extend({
+openerp.base.Homepage = openerp.base.Widget.extend({
});
-openerp.base.Preferences = openerp.base.Controller.extend({
+openerp.base.Preferences = openerp.base.Widget.extend({
});
-openerp.base.ImportExport = openerp.base.Controller.extend({
+openerp.base.ImportExport = openerp.base.Widget.extend({
});
-openerp.base.WebClient = openerp.base.Controller.extend({
+openerp.base.WebClient = openerp.base.Widget.extend({
init: function(element_id) {
- var self = this;
this._super(null, element_id);
- QWeb.add_template("xml/base.xml");
+ QWeb.add_template("/base/static/src/xml/base.xml");
var params = {};
- if(jQuery.param != undefined &&
- jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
+ if(jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
this.$element.addClass("kitten-mode-activated");
}
this.$element.html(QWeb.render("Interface", params));
- this.session = new openerp.base.Session("oe_errors");
- this.loading = new openerp.base.Loading(this.session, "oe_loading");
- this.crashmanager = new openerp.base.CrashManager(this.session);
+ this.session = new openerp.base.Session(this,"oe_errors");
+ this.loading = new openerp.base.Loading(this,"oe_loading");
+ this.crashmanager = new openerp.base.CrashManager(this);
this.crashmanager.start(false);
- // Do you autorize this ?
- openerp.base.Controller.prototype.notification = new openerp.base.Notification("oe_notification");
+ // Do you autorize this ? will be replaced by notify() in controller
+ openerp.base.Widget.prototype.notification = new openerp.base.Notification(this, "oe_notification");
- this.header = new openerp.base.Header(this.session, "oe_header");
- this.login = new openerp.base.Login(this.session, "oe_login");
+ this.header = new openerp.base.Header(this, "oe_header");
+ this.login = new openerp.base.Login(this, "oe_login");
this.header.on_logout.add(this.login.on_logout);
this.session.on_session_invalid.add(this.login.do_ask_login);
this.session.on_session_valid.add_last(this.header.do_update);
this.session.on_session_valid.add_last(this.on_logged);
- this.menu = new openerp.base.Menu(this.session, "oe_menu", "oe_secondary_menu");
+ this.menu = new openerp.base.Menu(this, "oe_menu", "oe_secondary_menu");
this.menu.on_action.add(this.on_menu_action);
},
start: function() {
@@ -1137,9 +666,9 @@ openerp.base.WebClient = openerp.base.Controller.extend({
this.notification.notify("OpenERP Client", "The openerp client has been initialized.");
},
on_logged: function() {
- this.action_manager = new openerp.base.ActionManager(this.session, "oe_app");
+ this.action_manager = new openerp.base.ActionManager(this, "oe_app");
this.action_manager.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) {
diff --git a/addons/base/static/src/js/core.js b/addons/base/static/src/js/core.js
new file mode 100644
index 00000000000..790542be7e4
--- /dev/null
+++ b/addons/base/static/src/js/core.js
@@ -0,0 +1,574 @@
+/*---------------------------------------------------------
+ * OpenERP controller framework
+ *--------------------------------------------------------*/
+
+openerp.base.core = function(openerp) {
+/**
+ * John Resig Class with factory improvement
+ */
+(function() {
+ var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
+ // The base Class implementation (does nothing)
+ this.Class = function(){};
+
+ // Create a new Class that inherits from this class
+ Class.extend = function(prop) {
+ var _super = this.prototype;
+
+ // Instantiate a base class (but only create the instance,
+ // don't run the init constructor)
+ initializing = true;
+ var prototype = new this();
+ initializing = false;
+
+ // Copy the properties over onto the new prototype
+ for (var name in prop) {
+ // Check if we're overwriting an existing function
+ prototype[name] = typeof prop[name] == "function" &&
+ typeof _super[name] == "function" && fnTest.test(prop[name]) ?
+ (function(name, fn){
+ return function() {
+ var tmp = this._super;
+
+ // Add a new ._super() method that is the same method
+ // but on the super-class
+ this._super = _super[name];
+
+ // The method only need to be bound temporarily, so we
+ // remove it when we're done executing
+ var ret = fn.apply(this, arguments);
+ this._super = tmp;
+
+ return ret;
+ };
+ })(name, prop[name]) :
+ prop[name];
+ }
+
+ // The dummy class constructor
+ function Class() {
+ // All construction is actually done in the init method
+ if ( !initializing && this.init ) {
+ var ret = this.init.apply(this, arguments);
+ if (ret) { return ret; }
+ }
+ return this;
+ }
+
+ // Populate our constructed prototype object
+ Class.prototype = prototype;
+
+ // Enforce the constructor to be what we expect
+ Class.constructor = Class;
+
+ // And make this class extendable
+ Class.extend = arguments.callee;
+
+ return Class;
+ };
+})();
+
+// todo change john resig class to keep window clean
+openerp.base.Class = window.Class
+
+openerp.base.callback = function(obj, method) {
+ var callback = function() {
+ var args = Array.prototype.slice.call(arguments);
+ var r;
+ for(var i = 0; i < callback.callback_chain.length; i++) {
+ var c = callback.callback_chain[i];
+ if(c.unique) {
+ callback.callback_chain.splice(i, 1);
+ i -= 1;
+ }
+ r = c.callback.apply(c.self, c.args.concat(args));
+ // TODO special value to stop the chain
+ // openerp.base.callback_stop
+ }
+ return r;
+ };
+ callback.callback_chain = [];
+ callback.add = function(f) {
+ if(typeof(f) == 'function') {
+ f = { callback: f, args: Array.prototype.slice.call(arguments, 1) };
+ }
+ f.self = f.self || null;
+ f.args = f.args || [];
+ f.unique = !!f.unique;
+ if(f.position == 'last') {
+ callback.callback_chain.push(f);
+ } else {
+ callback.callback_chain.unshift(f);
+ }
+ return callback;
+ };
+ callback.add_first = function(f) {
+ return callback.add.apply(null,arguments);
+ };
+ callback.add_last = function(f) {
+ return callback.add({
+ callback: f,
+ args: Array.prototype.slice.call(arguments, 1),
+ position: "last"
+ });
+ };
+
+ return callback.add({
+ callback: method,
+ self:obj,
+ args:Array.prototype.slice.call(arguments, 2)
+ });
+};
+
+/**
+ * Generates an inherited class that replaces all the methods by null methods (methods
+ * that does nothing and always return undefined).
+ *
+ * @param {Class} claz
+ * @param {dict} add Additional functions to override.
+ * @return {Class}
+ */
+openerp.base.generate_null_object_class = function(claz, add) {
+ var newer = {};
+ var copy_proto = function(prototype) {
+ for (var name in prototype) {
+ if(typeof prototype[name] == "function") {
+ newer[name] = function() {};
+ }
+ }
+ if (prototype.prototype)
+ copy_proto(prototype.prototype);
+ };
+ copy_proto(claz.prototype);
+ newer.init = openerp.base.Widget.prototype.init;
+ var tmpclass = claz.extend(newer);
+ return tmpclass.extend(add || {});
+};
+
+/**
+ * Base error for lookup failure
+ *
+ * @class
+ */
+openerp.base.NotFound = openerp.base.Class.extend( /** @lends openerp.base.NotFound# */ {
+});
+openerp.base.KeyNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.KeyNotFound# */ {
+ /**
+ * Thrown when a key could not be found in a mapping
+ *
+ * @constructs
+ * @extends openerp.base.NotFound
+ * @param {String} key the key which could not be found
+ */
+ init: function (key) {
+ this.key = key;
+ },
+ toString: function () {
+ return "The key " + this.key + " was not found";
+ }
+});
+openerp.base.ObjectNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.ObjectNotFound# */ {
+ /**
+ * Thrown when an object path does not designate a valid class or object
+ * in the openerp hierarchy.
+ *
+ * @constructs
+ * @extends openerp.base.NotFound
+ * @param {String} path the invalid object path
+ */
+ init: function (path) {
+ this.path = path;
+ },
+ toString: function () {
+ return "Could not find any object of path " + this.path;
+ }
+});
+openerp.base.Registry = openerp.base.Class.extend( /** @lends openerp.base.Registry# */ {
+ /**
+ * Stores a mapping of arbitrary key (strings) to object paths (as strings
+ * as well).
+ *
+ * Resolves those paths at query time in order to always fetch the correct
+ * object, even if those objects have been overloaded/replaced after the
+ * registry was created.
+ *
+ * An object path is simply a dotted name from the openerp root to the
+ * object pointed to (e.g. ``"openerp.base.Session"`` for an OpenERP
+ * session object).
+ *
+ * @constructs
+ * @param {Object} mapping a mapping of keys to object-paths
+ */
+ init: function (mapping) {
+ this.map = mapping || {};
+ },
+ /**
+ * Retrieves the object matching the provided key string.
+ *
+ * @param {String} key the key to fetch the object for
+ * @returns {Class} the stored class, to initialize
+ *
+ * @throws {openerp.base.KeyNotFound} if the object was not in the mapping
+ * @throws {openerp.base.ObjectNotFound} if the object path was invalid
+ */
+ get_object: function (key) {
+ var path_string = this.map[key];
+ if (path_string === undefined) {
+ throw new openerp.base.KeyNotFound(key);
+ }
+
+ var object_match = openerp;
+ var path = path_string.split('.');
+ // ignore first section
+ for(var i=1; i<path.length; ++i) {
+ object_match = object_match[path[i]];
+
+ if (object_match === undefined) {
+ throw new openerp.base.ObjectNotFound(path_string);
+ }
+ }
+ return object_match;
+ },
+ /**
+ * Tries a number of keys, and returns the first object matching one of
+ * the keys.
+ *
+ * @param {Array} keys a sequence of keys to fetch the object for
+ * @returns {Class} the first class found matching an object
+ *
+ * @throws {openerp.base.KeyNotFound} if none of the keys was in the mapping
+ * @trows {openerp.base.ObjectNotFound} if a found object path was invalid
+ */
+ get_any: function (keys) {
+ for (var i=0; i<keys.length; ++i) {
+ try {
+ return this.get_object(keys[i]);
+ } catch (e) {
+ if (e instanceof openerp.base.KeyNotFound) {
+ continue;
+ }
+ throw e;
+ }
+ }
+ throw new openerp.base.KeyNotFound(keys.join(','));
+ },
+ /**
+ * Adds a new key and value to the registry.
+ *
+ * This method can be chained.
+ *
+ * @param {String} key
+ * @param {String} object_path fully qualified dotted object path
+ * @returns {openerp.base.Registry} itself
+ */
+ add: function (key, object_path) {
+ this.map[key] = object_path;
+ return this;
+ },
+ /**
+ * Creates and returns a copy of the current mapping, with the provided
+ * mapping argument added in (replacing existing keys if needed)
+ *
+ * @param {Object} [mapping={}] a mapping of keys to object-paths
+ */
+ clone: function (mapping) {
+ return new openerp.base.Registry(
+ _.extend({}, this.map, mapping || {}));
+ }
+});
+
+/**
+ * Utility class that any class is allowed to extend to easy common manipulations.
+ *
+ * It provides rpc calls, callback on all methods preceded by "on_" or "do_" and a
+ * logging facility.
+ */
+openerp.base.SessionAware = openerp.base.Class.extend({
+ init: function(session) {
+ this.session = session;
+
+ // Transform on_* method into openerp.base.callbacks
+ for (var name in this) {
+ if(typeof(this[name]) == "function") {
+ this[name].debug_name = name;
+ // bind ALL function to this not only on_and _do ?
+ if((/^on_|^do_/).test(name)) {
+ this[name] = openerp.base.callback(this, this[name]);
+ }
+ }
+ }
+ },
+ /**
+ * Performs a JSON-RPC call
+ *
+ * @param {String} url endpoint url
+ * @param {Object} data RPC parameters
+ * @param {Function} success RPC call success callback
+ * @param {Function} error RPC call error callback
+ * @returns {jQuery.Deferred} deferred object for the RPC call
+ */
+ rpc: function(url, data, success, error) {
+ return this.session.rpc(url, data, success, error);
+ },
+ log: function() {
+ var args = Array.prototype.slice.call(arguments);
+ var caller = arguments.callee.caller;
+ // TODO add support for line number using
+ // https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
+ // args.unshift("" + caller.debug_name);
+ this.on_log.apply(this,args);
+ },
+ on_log: function() {
+ if(window.openerp.debug || (window.location.search.indexOf('?debug') !== -1)) {
+ var notify = false;
+ var body = false;
+ if(window.console) {
+ console.log(arguments);
+ } else {
+ body = true;
+ }
+ var a = Array.prototype.slice.call(arguments, 0);
+ for(var i = 0; i < a.length; i++) {
+ var v = a[i]==null ? "null" : a[i].toString();
+ if(i==0) {
+ notify = v.match(/^not/);
+ body = v.match(/^bod/);
+ }
+ if(body) {
+ $('<pre></pre>').text(v).appendTo($('body'));
+ }
+ if(notify && this.notification) {
+ this.notification.notify("Logging:",v);
+ }
+ }
+ }
+ }
+});
+
+/**
+ * Base class for all visual components. Provides a lot of functionalities helpful
+ * for the management of a part of the DOM.
+ *
+ * Widget handles:
+ * - Rendering with QWeb.
+ * - Life-cycle management and parenting (when a parent is destroyed, all its children are
+ * destroyed too).
+ * - Insertion in DOM.
+ *
+ * Widget also extends SessionAware for ease of use.
+ *
+ * Guide to create implementations of this class:
+ * ==============================================
+ *
+ * Here is a sample child class:
+ *
+ * MyWidget = openerp.base.Widget.extend({
+ * // the name of the QWeb template to use for rendering
+ * template: "MyQWebTemplate",
+ * // identifier prefix, useful to put an obvious one for debugging
+ * identifier_prefix: 'my-id-prefix-',
+ *
+ * init: function(parent) {
+ * this._super(parent);
+ * // stuff that you want to init before the rendering
+ * },
+ * start: function() {
+ * // stuff you want to make after the rendering, `this.$element` holds a value
+ * this.$element.find(".my_button").click(/* an example of event binding * /);
+ *
+ * // if you have some asynchronous operations, it's a good idea to return
+ * // a promise in start()
+ * var promise = this.rpc(...);
+ * return promise;
+ * }
+ * });
+ *
+ * Now this class can simply be used with the following syntax:
+ *
+ * var my_widget = new MyWidget(this);
+ * my_widget.appendTo($(".some-div"));
+ *
+ * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
+ * DOM inside the ".some-div" div and its events were binded.
+ *
+ * And of course, when you don't need that widget anymore, just do:
+ *
+ * my_widget.stop();
+ *
+ * That will kill the widget in a clean way and erase its content from the dom.
+ */
+openerp.base.Widget = openerp.base.SessionAware.extend({
+ /**
+ * The name of the QWeb template that will be used for rendering. Must be
+ * redefined in subclasses or the default render() method can not be used.
+ *
+ * @type string
+ */
+ template: null,
+ /**
+ * The prefix used to generate an id automatically. Should be redefined in
+ * subclasses. If it is not defined, a generic identifier will be used.
+ *
+ * @type string
+ */
+ identifier_prefix: 'generic-identifier-',
+ /**
+ * @constructs
+ * Construct the widget and set its parent if a parent is given.
+ *
+ * @param {Widget} parent Binds the current instance to the given Widget instance.
+ * When that widget is destroyed by calling stop(), the current instance will be
+ * destroyed too. Can be null.
+ * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
+ * to bind the current Widget to an already existing part of the DOM, which is not compatible
+ * with the DOM insertion methods provided by the current implementation of Widget. So
+ * for new components this argument should not be provided any more.
+ */
+ init: function(parent, /** @deprecated */ element_id) {
+ this._super((parent || {}).session);
+ // if given an element_id, try to get the associated DOM element and save
+ // a reference in this.$element. Else just generate a unique identifier.
+ this.element_id = element_id;
+ this.element_id = this.element_id || _.uniqueId(this.identifier_prefix);
+ var tmp = document.getElementById(this.element_id);
+ this.$element = tmp ? $(tmp) : undefined;
+
+ this.widget_parent = parent;
+ this.widget_children = [];
+ if(parent && parent.widget_children) {
+ parent.widget_children.push(this);
+ }
+ // useful to know if the widget was destroyed and should not be used anymore
+ this.widget_is_stopped = false;
+ },
+ /**
+ * Render the current widget and appends it to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ appendTo: function(target) {
+ var self = this;
+ return this._render_and_insert(function(t) {
+ self.$element.appendTo(t);
+ }, target);
+ },
+ /**
+ * Render the current widget and prepends it to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ prependTo: function(target) {
+ var self = this;
+ return this._render_and_insert(function(t) {
+ self.$element.prependTo(t);
+ }, target);
+ },
+ /**
+ * Render the current widget and inserts it after to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ insertAfter: function(target) {
+ var self = this;
+ return this._render_and_insert(function(t) {
+ self.$element.insertAfter(t);
+ }, target);
+ },
+ /**
+ * Render the current widget and inserts it before to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ insertBefore: function(target) {
+ var self = this;
+ return this._render_and_insert(function(t) {
+ self.$element.insertBefore(t);
+ }, target);
+ },
+ _render_and_insert: function(insertion, target) {
+ var rendered = this.render();
+ this.$element = $(rendered);
+ if (target instanceof openerp.base.Widget)
+ target = target.$element;
+ insertion(target);
+ return this.start();
+ },
+ /**
+ * Renders the widget using QWeb, `this.template` must be defined.
+ * The context given to QWeb contains the "widget" key that references `this`.
+ *
+ * @param {object} additional Additional context arguments to pass to the template.
+ */
+ render: function (additional) {
+ return QWeb.render(this.template, _.extend({widget: this}, additional || {}));
+ },
+ /**
+ * Method called after rendering. Mostly used to bind actions, perform asynchronous
+ * calls, etc...
+ *
+ * By convention, the method should return a promise to inform the caller when
+ * this widget has been initialized.
+ *
+ * @returns {jQuery.Deferred}
+ */
+ start: function() {
+ if (!this.$element) {
+ var tmp = document.getElementById(this.element_id);
+ this.$element = tmp ? $(tmp) : undefined;
+ }
+ return $.Deferred().done().promise();
+ },
+ /**
+ * Destroys the current widget, also destory all its children before destroying itself.
+ */
+ stop: function() {
+ _.each(_.clone(this.widget_children), function(el) {
+ el.stop();
+ });
+ if(this.$element != null) {
+ this.$element.remove();
+ }
+ if (this.widget_parent && this.widget_parent.widget_children) {
+ this.widget_parent.widget_children = _.without(this.widget_parent.widget_children, this);
+ }
+ this.widget_parent = null;
+ this.widget_is_stopped = true;
+ },
+ /**
+ * Inform the action manager to do an action. Of course, this suppose that
+ * the action manager can be found amongst the ancestors of the current widget.
+ * If that's not the case this method will simply return `false`.
+ */
+ do_action: function(action, on_finished) {
+ if (this.widget_parent) {
+ return this.widget_parent.do_action(action, on_finished);
+ }
+ return false;
+ },
+ rpc: function(url, data, success, error) {
+ var def = $.Deferred().then(success, error);
+ var self = this;
+ this._super(url, data). then(function() {
+ if (!self.widget_is_stopped)
+ def.resolve.apply(def, arguments);
+ }, function() {
+ if (!self.widget_is_stopped)
+ def.reject.apply(def, arguments);
+ });
+ return def.promise();
+ }
+});
+
+/**
+ * @deprecated
+ * For retro compatibility only, the only difference with is that render() uses
+ * directly `this` instead of context with a "widget" key.
+ */
+openerp.base.OldWidget = openerp.base.Widget.extend({
+ render: function (additional) {
+ return QWeb.render(this.template, _.extend(_.extend({}, this), additional || {}));
+ }
+});
+
+}
+// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js
index a8cdfb7f201..5545e32df17 100644
--- a/addons/base/static/src/js/data.js
+++ b/addons/base/static/src/js/data.js
@@ -1,7 +1,24 @@
openerp.base.data = function(openerp) {
-openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.base.DataGroup# */{
+/**
+ * Serializes the sort criterion array of a dataset into a form which can be
+ * consumed by OpenERP's RPC APIs.
+ *
+ * @param {Array} criterion array of fields, from first to last criteria, prefixed with '-' for reverse sorting
+ * @returns {String} SQL-like sorting string (``ORDER BY``) clause
+ */
+openerp.base.serialize_sort = function (criterion) {
+ return _.map(criterion,
+ function (criteria) {
+ if (criteria[0] === '-') {
+ return criteria.slice(1) + ' DESC';
+ }
+ return criteria + ' ASC';
+ }).join(', ');
+};
+
+openerp.base.DataGroup = openerp.base.Widget.extend( /** @lends openerp.base.DataGroup# */{
/**
* Management interface between views and grouped collections of OpenERP
* records.
@@ -13,7 +30,7 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas
* content of the current grouping level.
*
* @constructs
- * @extends openerp.base.Controller
+ * @extends openerp.base.Widget
*
* @param {openerp.base.Session} session Current OpenERP session
* @param {String} model name of the model managed by this DataGroup
@@ -22,18 +39,16 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas
* @param {Array} group_by sequence of fields by which to group
* @param {Number} [level=0] nesting level of the group
*/
- init: function(session, model, domain, context, group_by, level) {
+ init: function(parent, model, domain, context, group_by, level) {
+ this._super(parent, null);
if (group_by) {
if (group_by.length || context['group_by_no_leaf']) {
- return new openerp.base.ContainerDataGroup(
- session, model, domain, context, group_by, level);
+ return new openerp.base.ContainerDataGroup( this, model, domain, context, group_by, level);
} else {
- return new openerp.base.GrouplessDataGroup(
- session, model, domain, context, level);
+ return new openerp.base.GrouplessDataGroup( this, model, domain, context, level);
}
}
- this._super(session, null);
this.model = model;
this.context = context;
this.domain = domain;
@@ -42,8 +57,7 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas
},
cls: 'DataGroup'
});
-openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend(
- /** @lends openerp.base.ContainerDataGroup# */ {
+openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend( /** @lends openerp.base.ContainerDataGroup# */ {
/**
*
* @constructs
@@ -56,8 +70,8 @@ openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend(
* @param group_by
* @param level
*/
- init: function (session, model, domain, context, group_by, level) {
- this._super(session, model, domain, context, null, level);
+ init: function (parent, model, domain, context, group_by, level) {
+ this._super(parent, model, domain, context, null, level);
this.group_by = group_by;
},
@@ -120,29 +134,26 @@ openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend(
aggregates: aggregates
};
},
- fetch: function () {
+ fetch: function (fields) {
// internal method
var d = new $.Deferred();
var self = this;
- // disable caching for now, not sure what I should do there
- if (false && this.groups) {
- d.resolveWith(this, [this.groups]);
- } else {
- this.rpc('/base/group/read', {
- model: this.model,
- context: this.context,
- domain: this.domain,
- group_by_fields: this.group_by
- }, function () { }).then(function (response) {
- var data_groups = _(response.result).map(
- _.bind(self.transform_group, self));
- self.groups = data_groups;
- d.resolveWith(self, [data_groups]);
- }, function () {
- d.rejectWith.apply(d, self, [arguments]);
- });
- }
+ this.rpc('/base/group/read', {
+ model: this.model,
+ context: this.context,
+ domain: this.domain,
+ fields: _.uniq(this.group_by.concat(fields)),
+ group_by_fields: this.group_by,
+ sort: openerp.base.serialize_sort(this.sort)
+ }, function () { }).then(function (response) {
+ var data_groups = _(response).map(
+ _.bind(self.transform_group, self));
+ self.groups = data_groups;
+ d.resolveWith(self, [data_groups]);
+ }, function () {
+ d.rejectWith.apply(d, [self, arguments]);
+ });
return d.promise();
},
/**
@@ -163,24 +174,27 @@ openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend(
* records have for the current ``grouped_on`` field name).
* ``aggregates``
* a mapping of other aggregation fields provided by ``read_group``
+ *
+ * @param {Array} fields the list of fields to aggregate in each group, can be empty
+ * @param {Function} ifGroups function executed if any group is found (DataGroup.group_by is non-null and non-empty), called with a (potentially empty) list of groups as parameters.
+ * @param {Function} ifRecords function executed if there is no grouping left to perform, called with a DataSet instance as parameter
*/
- list: function (ifGroups, ifRecords) {
+ list: function (fields, ifGroups, ifRecords) {
var self = this;
- this.fetch().then(function (group_records) {
+ this.fetch(fields).then(function (group_records) {
ifGroups(_(group_records).map(function (group) {
var child_context = _.extend({}, self.context, group.__context);
return _.extend(
new openerp.base.DataGroup(
- self.session, self.model, group.__domain,
+ self, self.model, group.__domain,
child_context, child_context.group_by,
self.level + 1),
- group);
+ group, {sort: self.sort});
}));
});
}
});
-openerp.base.GrouplessDataGroup = openerp.base.DataGroup.extend(
- /** @lends openerp.base.GrouplessDataGroup# */ {
+openerp.base.GrouplessDataGroup = openerp.base.DataGroup.extend( /** @lends openerp.base.GrouplessDataGroup# */ {
/**
*
* @constructs
@@ -192,16 +206,16 @@ openerp.base.GrouplessDataGroup = openerp.base.DataGroup.extend(
* @param context
* @param level
*/
- init: function (session, model, domain, context, level) {
- this._super(session, model, domain, context, null, level);
+ init: function (parent, model, domain, context, level) {
+ this._super(parent, model, domain, context, null, level);
},
- list: function (ifGroups, ifRecords) {
- ifRecords(new openerp.base.DataSetSearch(this.session, this.model, this.context, this.domain));
+ list: function (fields, ifGroups, ifRecords) {
+ ifRecords(_.extend(
+ new openerp.base.DataSetSearch(this, this.model),
+ {domain: this.domain, context: this.context, _sort: this.sort}));
}
});
-
-openerp.base.StaticDataGroup = openerp.base.GrouplessDataGroup.extend(
- /** @lends openerp.base.StaticDataGroup# */ {
+openerp.base.StaticDataGroup = openerp.base.GrouplessDataGroup.extend( /** @lends openerp.base.StaticDataGroup# */ {
/**
* A specialization of groupless data groups, relying on a single static
* dataset as its records provider.
@@ -213,24 +227,25 @@ openerp.base.StaticDataGroup = openerp.base.GrouplessDataGroup.extend(
init: function (dataset) {
this.dataset = dataset;
},
- list: function (ifGroups, ifRecords) {
+ list: function (fields, ifGroups, ifRecords) {
ifRecords(this.dataset);
}
});
-openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.DataSet# */{
+openerp.base.DataSet = openerp.base.Widget.extend( /** @lends openerp.base.DataSet# */{
/**
* DateaManagement interface between views and the collection of selected
* OpenERP records (represents the view's state?)
*
* @constructs
- * @extends openerp.base.Controller
+ * @extends openerp.base.Widget
*
- * @param {openerp.base.Session} session current OpenERP session
* @param {String} model the OpenERP model this dataset will manage
*/
- init: function(session, model, context) {
- this._super(session);
+ init: function(source_controller, model, context) {
+ // we don't want the dataset to be a child of anything!
+ this._super(null);
+ this.session = source_controller ? source_controller.session : undefined;
this.model = model;
this.context = context || {};
this.index = null;
@@ -276,14 +291,18 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
* Read the indexed record.
*/
read_index: function (fields, callback) {
+ var def = $.Deferred().then(callback);
if (_.isEmpty(this.ids)) {
- return $.Deferred().reject().promise();
+ def.reject();
} else {
fields = fields || false;
- return this.read_ids([this.ids[this.index]], fields, function(records) {
- callback(records[0]);
+ return this.read_ids([this.ids[this.index]], fields).then(function(records) {
+ def.resolve(records[0]);
+ }, function() {
+ def.reject.apply(def, arguments);
});
}
+ return def.promise();
},
default_get: function(fields, callback) {
return this.rpc('/base/dataset/default_get', {
@@ -362,26 +381,24 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
return this.context;
}
});
-
openerp.base.DataSetStatic = openerp.base.DataSet.extend({
- init: function(session, model, context, ids) {
- this._super(session, model, context);
+ init: function(parent, model, context, ids) {
+ this._super(parent, model, context);
// all local records
this.ids = ids || [];
- if (this.ids.length) {
- this.index = 0;
- }
},
read_slice: function (fields, offset, limit, callback) {
var self = this;
offset = offset || 0;
var end_pos = limit && limit !== -1 ? offset + limit : undefined;
- this.read_ids(this.ids.slice(offset, end_pos), fields, callback);
+ return this.read_ids(this.ids.slice(offset, end_pos), fields, callback);
},
set_ids: function (ids) {
this.ids = ids;
- this.index = this.index <= this.ids.length - 1 ?
- this.index : (this.ids.length > 0 ? this.length - 1 : 0);
+ if (this.index !== null) {
+ this.index = this.index <= this.ids.length - 1 ?
+ this.index : (this.ids.length > 0 ? this.length - 1 : 0);
+ }
},
unlink: function(ids) {
this.on_unlink(ids);
@@ -391,10 +408,9 @@ openerp.base.DataSetStatic = openerp.base.DataSet.extend({
this.set_ids(_.without.apply(null, [this.ids].concat(ids)));
}
});
-
openerp.base.DataSetSearch = openerp.base.DataSet.extend({
- init: function(session, model, context, domain) {
- this._super(session, model, context);
+ init: function(parent, model, context, domain) {
+ this._super(parent, model, context);
this.domain = domain || [];
this._sort = [];
this.offset = 0;
@@ -413,7 +429,7 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
// return read_ids(ids.slice(start,start+limit),fields,callback)
}
}
- this.rpc('/base/dataset/search_read', {
+ return this.rpc('/base/dataset/search_read', {
model: this.model,
fields: fields,
domain: this.domain,
@@ -421,13 +437,12 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
sort: this.sort(),
offset: offset,
limit: limit
- }, function (records) {
- self.ids.splice(0, self.ids.length);
+ }, function (result) {
+ self.ids = result.ids;
self.offset = offset;
- for (var i=0; i < records.length; i++ ) {
- self.ids.push(records[i].id);
+ if (callback) {
+ callback(result.records);
}
- callback(records);
});
},
/**
@@ -445,16 +460,12 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
*/
sort: function (field, force_reverse) {
if (!field) {
- return _.map(this._sort, function (criteria) {
- if (criteria[0] === '-') {
- return criteria.slice(1) + ' DESC';
- }
- return criteria + ' ASC';
- }).join(', ');
+ return openerp.base.serialize_sort(this._sort);
}
-
var reverse = force_reverse || (this._sort[0] === field);
- this._sort = _.without(this._sort, field, '-' + field);
+ this._sort.splice.apply(
+ this._sort, [0, this._sort.length].concat(
+ _.without(this._sort, field, '-' + field)));
this._sort.unshift((reverse ? '-' : '') + field);
return undefined;
@@ -463,14 +474,15 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
var self = this;
return this._super(ids, function(result) {
self.ids = _.without.apply(_, [self.ids].concat(ids));
- self.index = self.index <= self.ids.length - 1 ?
- self.index : (self.ids.length > 0 ? self.ids.length -1 : 0);
+ if (this.index !== null) {
+ self.index = self.index <= self.ids.length - 1 ?
+ self.index : (self.ids.length > 0 ? self.ids.length -1 : 0);
+ }
if (callback)
callback(result);
}, error_callback);
}
});
-
openerp.base.BufferedDataSet = openerp.base.DataSetStatic.extend({
virtual_id_prefix: "one2many_v_id_",
virtual_id_regex: /one2many_v_id_.*/,
@@ -483,26 +495,34 @@ openerp.base.BufferedDataSet = openerp.base.DataSetStatic.extend({
var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data};
this.to_create.push(cached);
this.cache.push(cached);
- this.on_change();
var to_return = $.Deferred().then(callback);
- setTimeout(function() {to_return.resolve({result: cached.id});}, 0);
+ to_return.resolve({result: cached.id});
return to_return.promise();
},
write: function (id, data, callback) {
var self = this;
var record = _.detect(this.to_create, function(x) {return x.id === id;});
record = record || _.detect(this.to_write, function(x) {return x.id === id;});
+ var dirty = false;
if (record) {
+ for (k in data) {
+ if (record.values[k] === undefined || record.values[k] !== data[k]) {
+ dirty = true;
+ break;
+ }
+ }
$.extend(record.values, data);
} else {
+ dirty = true;
record = {id: id, values: data};
self.to_write.push(record);
}
var cached = _.detect(this.cache, function(x) {return x.id === id;});
$.extend(cached.values, record.values);
- this.on_change();
+ if (dirty)
+ this.on_change();
var to_return = $.Deferred().then(callback);
- setTimeout(function () {to_return.resolve({result: true});}, 0);
+ to_return.resolve({result: true});
return to_return.promise();
},
unlink: function(ids, callback, error_callback) {
@@ -576,7 +596,6 @@ openerp.base.BufferedDataSet = openerp.base.DataSetStatic.extend({
return completion.promise();
}
});
-
openerp.base.ReadOnlyDataSetSearch = openerp.base.DataSetSearch.extend({
create: function(data, callback, error_callback) {
this.on_create(data);
@@ -586,12 +605,12 @@ openerp.base.ReadOnlyDataSetSearch = openerp.base.DataSetSearch.extend({
},
on_create: function(data) {},
write: function (id, data, callback) {
- this.on_write(id);
+ this.on_write(id, data);
var to_return = $.Deferred().then(callback);
setTimeout(function () {to_return.resolve({"result": true});}, 0);
return to_return.promise();
},
- on_write: function(id) {},
+ on_write: function(id, data) {},
unlink: function(ids, callback, error_callback) {
this.on_unlink(ids);
var to_return = $.Deferred().then(callback);
diff --git a/addons/base/static/src/js/data_export.js b/addons/base/static/src/js/data_export.js
new file mode 100644
index 00000000000..ad440302c69
--- /dev/null
+++ b/addons/base/static/src/js/data_export.js
@@ -0,0 +1,402 @@
+openerp.base.data_export = function(openerp) {
+openerp.base.DataExport = openerp.base.Dialog.extend({
+ init: function(parent, dataset) {
+ this._super(parent);
+ this.dataset = dataset;
+ },
+ start: function() {
+ var self = this;
+ self._super(false);
+ self.template = 'ExportTreeView';
+ self.dialog_title = "Export Data";
+ self.open({
+ modal: true,
+ width: '55%',
+ height: 'auto',
+ position: 'top',
+ buttons : {
+ "Close" : function() {
+ self.close();
+ },
+ "Export To File" : function() {
+ self.on_click_export_data();
+ }
+ },
+ close: function(event, ui){ self.close();}
+ });
+ self.on_show_exists_export_list();
+ self.$element.removeClass('ui-dialog-content ui-widget-content');
+ self.$element.find('#add_field').click(function() {
+ if ($('#field-tree-structure tr.ui-selected')) {
+ var fld = self.$element.find('#field-tree-structure tr.ui-selected').find('a');
+ for (var i = 0; i < fld.length; i++) {
+ var id = $(fld[i]).attr('id').split('-')[1];
+ var string = $(fld[i]).attr('string');
+ self.add_field(id, string);
+ }
+ self.$element.find('#field-tree-structure tr').removeClass('ui-selected');
+ }
+ });
+ self.$element.find('#remove_field').click(function() {
+ self.$element.find('#fields_list option:selected').remove();
+ });
+ self.$element.find('#remove_all_field').click(function() {
+ self.$element.find('#fields_list option').remove();
+ });
+ self.$element.find('#export_new_list').click(function() {
+ self.on_show_save_list();
+ });
+ var import_comp = self.$element.find('#import_compat option:selected').val(),
+ params = {
+ import_compat: parseInt(import_comp)
+ };
+ self.rpc('/base/export/get_fields', { model: self.dataset.model, params: params }, self.on_show_data);
+
+ self.$element.find('#import_compat').change(function() {
+ self.$element.find('#fields_list option').remove();
+ self.$element.find('#field-tree-structure').remove();
+ var import_comp = self.$element.find("#import_compat option:selected").val();
+ if (import_comp) {
+ var params = {
+ import_compat: parseInt(import_comp)
+ }
+ self.rpc("/base/export/get_fields", { model: self.dataset.model, params: params}, self.on_show_data);
+ }
+ });
+ },
+ on_show_exists_export_list: function() {
+ var self = this;
+ if (self.$element.find('#saved_export_list').is(':hidden')) {
+ self.$element.find('#ExistsExportList').show();
+ } else {
+ this.rpc('/base/export/exist_export_lists', { 'model': this.dataset.model}, function(export_list) {
+ if (export_list.length) {
+ self.$element.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list}));
+ self.$element.find('#saved_export_list').change(function() {
+ self.$element.find('#fields_list option').remove();
+ var export_id = self.$element.find('#saved_export_list option:selected').val();
+ if (export_id) {
+ self.rpc('/base/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}, self.do_load_export_field);
+ }
+ });
+ self.$element.find('#delete_export_list').click(function() {
+ var select_exp = self.$element.find('#saved_export_list option:selected');
+ if (select_exp.val()) {
+ self.rpc('/base/export/delete_export', { export_id: parseInt(select_exp.val())}, {});
+ select_exp.remove();
+ if (self.$element.find('#saved_export_list option').length <= 1) {
+ self.$element.find('#ExistsExportList').hide();
+ }
+ }
+ });
+ }
+ });
+ }
+ },
+ do_load_export_field: function(field_list) {
+ var export_node = this.$element.find("#fields_list");
+ for (var key in field_list) {
+ export_node.append(new Option(field_list[key], key));
+ }
+ },
+ on_show_save_list: function() {
+ var self = this;
+ var current_node = self.$element.find("#savenewlist");
+ if (!(current_node.find("label")).length) {
+ current_node.append(QWeb.render('ExportNewList'));
+ current_node.find("#add_export_list").click(function() {
+ var value = current_node.find("#savelist_name").val();
+ if (value) {
+ self.do_save_export_list(value);
+ } else {
+ alert("Pleae Enter Save Field List Name");
+ }
+ });
+ } else {
+ if (current_node.is(':hidden')) {
+ current_node.show();
+ current_node.find("#savelist_name").val("");
+ } else {
+ current_node.hide();
+ }
+ }
+ },
+ do_save_export_list: function(value) {
+ var self = this;
+ var export_field = self.get_fields();
+ if (export_field.length) {
+ self.rpc("/base/export/save_export_lists", {"model": self.dataset.model, "name":value, "field_list":export_field}, function(exp_id) {
+ if (exp_id) {
+ if (self.$element.find("#saved_export_list").length > 0) {
+ self.$element.find("#saved_export_list").append(new Option(value, exp_id));
+ } else {
+ self.on_show_exists_export_list();
+ }
+ if (self.$element.find("#saved_export_list").is(":hidden")) {
+ self.on_show_exists_export_list();
+ }
+ }
+ });
+ self.on_show_save_list();
+ self.$element.find("#fields_list option").remove();
+ }
+ },
+ on_click: function(id, result) {
+ var self = this;
+ self.field_id = id.split("-")[1];
+ var is_loaded = 0;
+ _.each(result, function(record) {
+ if (record['id'] == self.field_id && (record['children']).length >= 1) {
+ var model = record['params']['model'],
+ prefix = record['params']['prefix'],
+ name = record['params']['name'];
+ $(record['children']).each(function(e, childid) {
+ if (self.$element.find("tr[id='treerow-" + childid + "']").length > 0) {
+ if (self.$element.find("tr[id='treerow-" + childid + "']").is(':hidden')) {
+ is_loaded = -1;
+ } else {
+ is_loaded++;
+ }
+ }
+ });
+ if (is_loaded == 0) {
+ if (self.$element.find("tr[id='treerow-" + self.field_id +"']").find('img').attr('src') === '/base/static/src/img/expand.gif') {
+ if (model) {
+ var import_comp = self.$element.find("#import_compat option:selected").val();
+ var params = {
+ import_compat: parseInt(import_comp),
+ parent_field_type : record['field_type']
+ }
+ self.rpc("/base/export/get_fields", {
+ model: model,
+ prefix: prefix,
+ name: name,
+ field_parent : self.field_id,
+ params: params
+ }, function(results) {
+ self.on_show_data(results);
+ });
+ }
+ }
+ } else if (is_loaded > 0) {
+ self.showcontent(self.field_id, true);
+ } else {
+ self.showcontent(self.field_id, false);
+ }
+ }
+ });
+ },
+ on_show_data: function(result) {
+ var self = this;
+ var imp_cmpt = parseInt(self.$element.find("#import_compat option:selected").val());
+ var current_tr = self.$element.find("tr[id='treerow-" + self.field_id + "']");
+ if (current_tr.length >= 1) {
+ current_tr.find('img').attr('src','/base/static/src/img/collapse.gif');
+ current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result}));
+ } else {
+ self.$element.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result}));
+ }
+ _.each(result, function(record) {
+ if ((record.field_type == "one2many") && imp_cmpt) {
+ var o2m_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
+ o2m_fld.addClass("oe_export_readonlyfield");
+ }
+ if ((record.required == true) || record.required == "True") {
+ var required_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
+ required_fld.addClass("oe_export_requiredfield");
+ }
+ self.$element.find("img[id='parentimg-" + record.id +"']").click(function() {
+ self.on_click(this.id, result);
+ });
+
+ self.$element.find("tr[id='treerow-" + record.id + "']").click(function(e) {
+ if (e.shiftKey == true) {
+ var frst_click, scnd_click = '';
+ if (self.row_index == 0) {
+ self.row_index = this.rowIndex;
+ frst_click = self.$element.find("tr[id^='treerow-']")[self.row_index-1];
+ $(frst_click).addClass("ui-selected");
+ } else {
+ if (this.rowIndex >=self.row_index) {
+ for (i = (self.row_index-1); i < this.rowIndex; i++) {
+ scnd_click = self.$element.find("tr[id^='treerow-']")[i];
+ if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(scnd_click).addClass("ui-selected");
+ }
+ }
+ } else {
+ for (i = (self.row_index-1); i >= (this.rowIndex-1); i--) {
+ scnd_click = self.$element.find("tr[id^='treerow-']")[i];
+ if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(scnd_click).addClass("ui-selected");
+ }
+ }
+ }
+ }
+ }
+ self.row_index = this.rowIndex;
+
+ self.$element.find("tr[id='treerow-" + record.id + "']").keyup(function(e) {
+ self.row_index = 0;
+ });
+ var o2m_selection = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
+ if ($(o2m_selection).hasClass("oe_export_readonlyfield")) {
+ return false;
+ }
+ var selected = self.$element.find("tr.ui-selected");
+ if ($(this).hasClass("ui-selected") && (e.ctrlKey == true)) {
+ $(this).find('a').blur();
+ $(this).removeClass("ui-selected");
+ } else if ($(this).hasClass("ui-selected") && (e.ctrlKey == false) && (e.shiftKey == false)) {
+ selected.find('a').blur();
+ selected.removeClass("ui-selected");
+ $(this).find('a').focus();
+ $(this).addClass("ui-selected");
+ } else if (!$(this).hasClass("ui-selected") && (e.ctrlKey == false) && (e.shiftKey == false)) {
+ selected.find('a').blur();
+ selected.removeClass("ui-selected");
+ $(this).find('a').focus();
+ $(this).addClass("ui-selected");
+ } else if (!$(this).hasClass("ui-selected") && (e.ctrlKey == true)) {
+ $(this).find('a').focus();
+ $(this).addClass("ui-selected");
+ }
+ return false;
+ });
+
+ self.$element.find("tr[id='treerow-" + record.id + "']").keydown(function(e) {
+ var keyCode = e.keyCode || e.which;
+ arrow = {left: 37, up: 38, right: 39, down: 40 };
+ switch (keyCode) {
+ case arrow.left:
+ if ($(this).find('img').attr('src') === '/base/static/src/img/collapse.gif') {
+ self.on_click(this.id, result);
+ }
+ break;
+ case arrow.up:
+ var elem = this;
+ $(elem).removeClass("ui-selected");
+ while ($(elem).prev().is(":visible") == false) {
+ elem = $(elem).prev();
+ }
+ if (!$(elem).prev().find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(elem).prev().addClass("ui-selected");
+ }
+ $(elem).prev().find('a').focus();
+ break;
+ case arrow.right:
+ if ($(this).find('img').attr('src') == '/base/static/src/img/expand.gif') {
+ self.on_click(this.id, result);
+ }
+ break;
+ case arrow.down:
+ var elem = this;
+ $(elem).removeClass("ui-selected");
+ while($(elem).next().is(":visible") == false) {
+ elem = $(elem).next();
+ }
+ if (!$(elem).next().find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(elem).next().addClass("ui-selected");
+ }
+ $(elem).next().find('a').focus();
+ break;
+ }
+ });
+ self.$element.find("tr[id='treerow-" + record.id + "']").dblclick(function(e) {
+ var $o2m_selection = self.$element.find("tr[id^='treerow-" + record.id + "']").find('#tree-column');
+ if (!$o2m_selection.hasClass("oe_export_readonlyfield")) {
+ var field_id = $(this).find("a").attr("id");
+ if (field_id) {
+ self.add_field(field_id.split('-')[1], $(this).find("a").attr("string"));
+ }
+ }
+ });
+ });
+ self.$element.find('#fields_list').mouseover(function(event) {
+ if (event.relatedTarget) {
+ if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) {
+ var field_id = event.relatedTarget.attributes["id"]["value"];
+ if (field_id && field_id.split("-")[0] === 'export') {
+ if (!self.$element.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]);
+ }
+ }
+ }
+ }
+ });
+ },
+ showcontent: function(id, flag) {
+ // show & hide the contents
+ var first_child = this.$element.find("tr[id='treerow-" + id + "']").find('img');
+ if (flag) {
+ first_child.attr('src', '/base/static/src/img/expand.gif');
+ }
+ else {
+ first_child.attr('src', '/base/static/src/img/collapse.gif');
+ }
+ var child_field = this.$element.find("tr[id^='treerow-" + id +"/']");
+ var child_len = (id.split("/")).length + 1;
+ for (var i = 0; i < child_field.length; i++) {
+ if (flag) {
+ $(child_field[i]).hide();
+ } else {
+ if (child_len == (child_field[i].id.split("/")).length) {
+ if ($(child_field[i]).find('img').attr('src') == '/base/static/src/img/collapse.gif') {
+ $(child_field[i]).find('img').attr('src', '/base/static/src/img/expand.gif');
+ }
+ $(child_field[i]).show();
+ }
+ }
+ }
+ },
+ add_field: function(field_id, string) {
+ var field_list = this.$element.find('#fields_list');
+ if (this.$element.find("#fields_list option[value='" + field_id + "']") && !this.$element.find("#fields_list option[value='" + field_id + "']").length) {
+ field_list.append(new Option(string, field_id));
+ }
+ },
+ get_fields: function() {
+ var export_field = [];
+ this.$element.find("#fields_list option").each(function() {
+ export_field.push($(this).val());
+ });
+ if (!export_field.length) {
+ alert('Please select fields to save export list...');
+ }
+ return export_field;
+ },
+ on_click_export_data: function() {
+ var self = this;
+ var export_field = {};
+ var flag = true;
+ self.$element.find("#fields_list option").each(function() {
+ export_field[$(this).val()] = $(this).text();
+ flag = false;
+ });
+ if (flag) {
+ alert('Please select fields to export...');
+ return;
+ }
+
+ var import_comp = self.$element.find("#import_compat option:selected").val(),
+ export_format = self.$element.find("#export_format").val();
+
+ self.rpc("/base/export/export_data", {
+ model: self.dataset.model,
+ fields: export_field,
+ ids: self.dataset.ids,
+ domain: self.dataset.domain,
+ import_compat: parseInt(import_comp),
+ export_format: export_format
+ }, function(data) {
+ window.location = "data:text/csv/excel;charset=utf8," + data;
+ self.close();
+ });
+ },
+ close: function() {
+ $(this.$dialog).remove();
+ this._super();
+ }
+});
+
+};
diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js
index e4cc0172789..f8e2e91667d 100644
--- a/addons/base/static/src/js/form.js
+++ b/addons/base/static/src/js/form.js
@@ -1,7 +1,7 @@
openerp.base.form = function (openerp) {
openerp.base.views.add('form', 'openerp.base.FormView');
-openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{
+openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormView# */{
/**
* Indicates that this view is not searchable, and thus that no search
* view should be displayed (if there is one active).
@@ -17,9 +17,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
*
* @property {openerp.base.Registry} registry=openerp.base.form.widgets widgets registry for this form view instance
*/
- init: function(view_manager, session, element_id, dataset, view_id) {
- this._super(session, element_id);
- this.view_manager = view_manager || new openerp.base.NullViewManager();
+ init: function(parent, element_id, dataset, view_id, options) {
+ this._super(parent, element_id);
+ this.set_default_options();
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
@@ -30,26 +30,36 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.datarecord = {};
this.ready = false;
this.show_invalid = true;
- this.touched = false;
- this.flags = this.view_manager.flags || {};
+ this.dirty = false;
this.default_focus_field = null;
this.default_focus_button = null;
this.registry = openerp.base.form.widgets;
this.has_been_loaded = $.Deferred();
this.$form_header = null;
+ this.options = options || {};
+ _.defaults(this.options, {"always_show_new_button": true});
},
start: function() {
//this.log('Starting FormView '+this.model+this.view_id)
if (this.embedded_view) {
- return $.Deferred().then(this.on_loaded).resolve({fields_view: this.embedded_view});
+ var def = $.Deferred().then(this.on_loaded);
+ var self = this;
+ setTimeout(function() {def.resolve({fields_view: self.embedded_view});}, 0);
+ return def.promise();
} else {
- var context = new openerp.base.CompoundContext(this.dataset.context);
- if (this.view_manager.action && this.view_manager.action.context) {
- context.add(this.view_manager.action.context);
- }
+ var context = new openerp.base.CompoundContext(this.dataset.get_context());
return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id,
- toolbar:!!this.flags.sidebar, context: context}, this.on_loaded);
+ toolbar: this.options.sidebar, context: context}, this.on_loaded);
+ }
+ },
+ stop: function() {
+ if (this.sidebar) {
+ this.sidebar.attachments.stop();
+ this.sidebar.stop();
}
+ _.each(this.widgets, function(w) {
+ w.stop();
+ });
},
on_loaded: function(data) {
var self = this;
@@ -71,22 +81,39 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.$form_header.find('button.oe_form_button_cancel').click(this.do_cancel);
this.$form_header.find('button.oe_form_button_new').click(this.on_button_new);
- this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
+ this.$form_header.find('button.oe_get_xml_view').click(function() {
+ $('<xmp>' + openerp.base.json_node_to_xml(self.fields_view.arch, true) + '</xmp>').dialog({ width: '95%', height: 600});
+ });
+
+ if (this.options.sidebar && this.options.sidebar_id) {
+ this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id);
+ this.sidebar.start();
+ this.sidebar.do_unfold();
+ this.sidebar.attachments = new openerp.base.form.SidebarAttachments(this.sidebar, this.sidebar.add_section('attachments', "Attachments"), this);
+ this.sidebar.add_toolbar(data.fields_view.toolbar);
+ this.set_common_sidebar_sections(this.sidebar);
+ }
this.has_been_loaded.resolve();
},
do_show: function () {
- var self = this;
+ var promise;
if (this.dataset.index === null) {
// null index means we should start a new record
- this.on_button_new();
+ promise = this.on_button_new();
} else {
- this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
+ promise = this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
}
- self.$element.show();
- this.view_manager.sidebar.do_refresh(true);
+ this.$element.show();
+ if (this.sidebar) {
+ this.sidebar.$element.show();
+ }
+ return promise;
},
do_hide: function () {
this.$element.hide();
+ if (this.sidebar) {
+ this.sidebar.$element.hide();
+ }
},
on_record_loaded: function(record) {
if (!record) {
@@ -95,28 +122,30 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
if (!record.id) {
this.$form_header.find('.oe_form_on_create').show();
this.$form_header.find('.oe_form_on_update').hide();
- this.$form_header.find('button.oe_form_button_new').hide();
+ if (!this.options["always_show_new_button"]) {
+ this.$form_header.find('button.oe_form_button_new').hide();
+ }
} else {
this.$form_header.find('.oe_form_on_create').hide();
this.$form_header.find('.oe_form_on_update').show();
this.$form_header.find('button.oe_form_button_new').show();
}
- this.touched = false;
+ this.dirty = false;
this.datarecord = record;
for (var f in this.fields) {
var field = this.fields[f];
- field.touched = false;
+ field.dirty = false;
field.set_value(this.datarecord[f] || false);
field.validate();
}
if (!record.id) {
// New record: Second pass in order to trigger the onchanges
- this.touched = true;
+ this.dirty = true;
this.show_invalid = false;
for (var f in record) {
var field = this.fields[f];
if (field) {
- field.touched = true;
+ field.dirty = true;
this.do_onchange(field);
}
}
@@ -124,7 +153,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.on_form_changed();
this.show_invalid = this.ready = true;
this.do_update_pager(record.id == null);
- this.do_update_sidebar();
+ if (this.sidebar) {
+ this.sidebar.attachments.do_update();
+ }
if (this.default_focus_field) {
this.default_focus_field.focus();
}
@@ -132,7 +163,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
on_form_changed: function() {
for (var w in this.widgets) {
w = this.widgets[w];
- w.process_attrs();
+ w.process_modifiers();
w.update_dom();
}
},
@@ -166,19 +197,24 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.ready = false;
var onchange = _.trim(widget.node.attrs.on_change);
var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
- console.log("Onchange triggered for field '%s' -> %s", widget.name, onchange);
if (call) {
var method = call[1], args = [];
+ var context_index = null;
var argument_replacement = {
- 'False' : false,
- 'True' : true,
- 'None' : null
+ 'False' : function() {return false;},
+ 'True' : function() {return true;},
+ 'None' : function() {return null;},
+ 'context': function(i) {
+ context_index = i;
+ var ctx = widget.build_context ? widget.build_context() : {};
+ return ctx;
+ }
}
var parent_fields = null;
- _.each(call[2].split(','), function(a) {
+ _.each(call[2].split(','), function(a, i) {
var field = _.trim(a);
if (field in argument_replacement) {
- args.push(argument_replacement[field]);
+ args.push(argument_replacement[field](i));
return;
} else if (self.fields[field]) {
var value = self.fields[field].get_on_change_value();
@@ -192,7 +228,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
}
var p_val = parent_fields[_.trim(splitted[1])];
if (p_val !== undefined) {
- args.push(value ? value : false);
+ args.push(p_val == null ? false : p_val);
return;
}
}
@@ -207,7 +243,8 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
return this.rpc(ajax, {
model: this.dataset.model,
method: method,
- args: [(this.datarecord.id == null ? [] : [this.datarecord.id])].concat(args)
+ args: [(this.datarecord.id == null ? [] : [this.datarecord.id])].concat(args),
+ context_id: context_index === null ? null : context_index + 1
}, function(response) {
self.on_processed_onchange(response, processed);
});
@@ -217,28 +254,26 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
}
},
on_processed_onchange: function(response, processed) {
- var result = response.result;
+ var result = response;
if (result.value) {
- console.log(" |-> Onchange Response :", result.value);
for (var f in result.value) {
var field = this.fields[f];
+ // If field is not defined in the view, just ignore it
if (field) {
var value = result.value[f];
processed.push(field.name);
if (field.get_value() != value) {
- console.log(" |-> Onchange Action : change '%s' value from '%s' to '%s'", field.name, field.get_value(), value);
field.set_value(value);
+ field.dirty = true;
if (_.indexOf(processed, field.name) < 0) {
this.do_onchange(field, processed);
}
}
- } else {
- this.log("warning : on_processed_onchange can't find field " + field, result);
}
}
this.on_form_changed();
}
- if (result.warning) {
+ if (!_.isEmpty(result.warning)) {
$(QWeb.render("DialogWarning", result.warning)).dialog({
modal: true,
buttons: {
@@ -249,17 +284,20 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
});
}
if (result.domain) {
- // Will be removed ?
+ // TODO:
}
this.ready = true;
},
on_button_new: function() {
var self = this;
+ var def = $.Deferred();
$.when(this.has_been_loaded).then(function() {
- self.dataset.default_get(_.keys(self.fields_view.fields), function(result) {
- self.on_record_loaded(result.result);
- });
+ self.dataset.default_get(
+ _.keys(self.fields_view.fields)).then(self.on_record_loaded).then(function() {
+ def.resolve();
+ });
});
+ return def.promise();
},
/**
* Triggers saving the form's record. Chooses between creating a new
@@ -274,37 +312,40 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
if (!this.ready) {
return false;
}
- var invalid = false,
+ var form_dirty = false,
+ form_invalid = false,
values = {},
first_invalid_field = null;
for (var f in this.fields) {
f = this.fields[f];
- if (f.invalid) {
- invalid = true;
+ if (!f.is_valid()) {
+ form_invalid = true;
f.update_dom();
if (!first_invalid_field) {
first_invalid_field = f;
}
- } else if (f.touched) {
+ } else if (f.is_dirty()) {
+ form_dirty = true;
values[f.name] = f.get_value();
}
}
- if (invalid) {
+ if (form_invalid) {
first_invalid_field.focus();
this.on_invalid();
return false;
- } else {
+ } else if (form_dirty) {
this.log("About to save", values);
if (!this.datarecord.id) {
- this.dataset.create(values, function(r) {
+ return this.dataset.create(values, function(r) {
self.on_created(r, success, prepend_on_create);
});
} else {
- this.dataset.write(this.datarecord.id, values, function(r) {
+ return this.dataset.write(this.datarecord.id, values, function(r) {
self.on_saved(r, success);
});
}
- return true;
+ } else {
+ return false;
}
},
do_save_edit: function() {
@@ -318,7 +359,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
on_invalid: function() {
var msg = "<ul>";
_.each(this.fields, function(f) {
- if (f.invalid) {
+ if (!f.is_valid()) {
msg += "<li>" + f.string + "</li>";
}
});
@@ -362,7 +403,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.dataset.index = 0;
}
this.do_update_pager();
- this.do_update_sidebar();
+ if (this.sidebar) {
+ this.sidebar.attachments.do_update();
+ }
this.notification.notify("Record created", "The record has been created with id #" + this.datarecord.id);
if (success) {
success(_.extend(r, {created: true}));
@@ -379,34 +422,55 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
do_cancel: function () {
this.notification.notify("Cancelling form");
},
- do_update_sidebar: function() {
- if (this.flags.sidebar === false) {
- return;
+ reload: function() {
+ if (this.datarecord.id) {
+ this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
+ } else {
+ this.on_button_new();
}
- if (!this.datarecord.id) {
+ },
+ get_fields_values: function() {
+ var values = {};
+ _.each(this.fields, function(value, key) {
+ var val = value.get_value();
+ values[key] = val;
+ });
+ return values;
+ }
+});
+
+/** @namespace */
+openerp.base.form = {};
+
+openerp.base.form.SidebarAttachments = openerp.base.Widget.extend({
+ init: function(parent, element_id, form_view) {
+ this._super(parent, element_id);
+ this.view = form_view;
+ },
+ do_update: function() {
+ if (!this.view.datarecord.id) {
this.on_attachments_loaded([]);
} else {
- // TODO fme: modify this so it doesn't try to load attachments when there is not sidebar
- /*this.rpc('/base/dataset/search_read', {
- model: 'ir.attachment',
- fields: ['name', 'url', 'type'],
- domain: [['res_model', '=', this.dataset.model], ['res_id', '=', this.datarecord.id], ['type', 'in', ['binary', 'url']]],
- context: this.dataset.context
- }, this.on_attachments_loaded);*/
+ (new openerp.base.DataSetSearch(
+ this, 'ir.attachment', this.view.dataset.get_context(),
+ [['res_model', '=', this.view.dataset.model],
+ ['res_id', '=', this.view.datarecord.id],
+ ['type', 'in', ['binary', 'url']]])).read_slice(
+ ['name', 'url', 'type'], false, false,
+ this.on_attachments_loaded);
}
},
on_attachments_loaded: function(attachments) {
- this.$sidebar = this.view_manager.sidebar.$element.find('.sidebar-attachments');
this.attachments = attachments;
- this.$sidebar.html(QWeb.render('FormView.sidebar.attachments', this));
- this.$sidebar.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete);
- this.$sidebar.find('.oe-binary-file').change(this.on_attachment_changed);
+ this.$element.html(QWeb.render('FormView.sidebar.attachments', this));
+ this.$element.find('.oe-binary-file').change(this.on_attachment_changed);
+ this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete);
},
on_attachment_changed: function(e) {
- window[this.element_id + '_iframe'] = this.do_update_sidebar;
+ window[this.element_id + '_iframe'] = this.do_update;
var $e = $(e.target);
if ($e.val() != '') {
- this.$sidebar.find('form.oe-binary-form').submit();
+ this.$element.find('form.oe-binary-form').submit();
$e.parent().find('input[type=file]').attr('disabled', 'true');
$e.parent().find('button').attr('disabled', 'true').find('img, span').toggle();
}
@@ -423,26 +487,9 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
self.notification.notify("Delete an attachment", "The attachment '" + name + "' has been deleted");
});
}
- },
- reload: function() {
- if (this.datarecord.id) {
- this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
- } else {
- this.on_button_new();
- }
- },
- get_fields_values: function() {
- var values = {};
- _.each(this.fields, function(value, key) {
- values[key] = value.get_value();
- });
- return values;
}
});
-/** @namespace */
-openerp.base.form = {};
-
openerp.base.form.compute_domain = function(expr, fields) {
var stack = [];
for (var i = expr.length - 1; i >= 0; i--) {
@@ -496,23 +543,23 @@ openerp.base.form.compute_domain = function(expr, fields) {
stack.push(!_(val).contains(field));
break;
default:
- this.log("Unsupported operator in attrs :", op);
+ this.log("Unsupported operator in modifiers :", op);
}
}
return _.all(stack);
};
-openerp.base.form.Widget = openerp.base.Controller.extend({
+openerp.base.form.Widget = openerp.base.Widget.extend({
template: 'Widget',
init: function(view, node) {
this.view = view;
this.node = node;
- this.attrs = JSON.parse(this.node.attrs.attrs || '{}');
+ this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}');
this.type = this.type || node.tag;
this.element_name = this.element_name || this.type;
this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_");
- this._super(this.view.session, this.element_id);
+ this._super(view, this.element_id);
this.view.widgets[this.element_id] = this;
this.children = node.children;
@@ -520,15 +567,18 @@ openerp.base.form.Widget = openerp.base.Controller.extend({
this.string = this.string || node.attrs.string;
this.help = this.help || node.attrs.help;
- this.invisible = (node.attrs.invisible == '1');
+ this.invisible = this.modifiers['invisible'] === true;
},
start: function() {
this.$element = $('#' + this.element_id);
},
- process_attrs: function() {
+ stop: function() {
+ this.$element.remove();
+ },
+ process_modifiers: function() {
var compute_domain = openerp.base.form.compute_domain;
- for (var a in this.attrs) {
- this[a] = compute_domain(this.attrs[a], this.view.fields);
+ for (var a in this.modifiers) {
+ this[a] = compute_domain(this.modifiers[a], this.view.fields);
}
},
update_dom: function() {
@@ -583,7 +633,10 @@ openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
}
},
handle_node: function(node) {
- var type = this.view.fields_view.fields[node.attrs.name] || {};
+ var type = {};
+ if (node.tag == 'field') {
+ type = this.view.fields_view.fields[node.attrs.name] || {};
+ }
var widget = new (this.view.registry.get_any(
[node.attrs.widget, type.type, node.tag])) (this.view, node);
if (node.tag == 'field') {
@@ -593,14 +646,15 @@ openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
if (node.attrs.nolabel != '1') {
var label = new (this.view.registry.get_object('label')) (this.view, node);
label["for"] = widget;
- this.add_widget(label);
+ this.add_widget(label, widget.colspan + 1);
}
}
this.add_widget(widget);
},
- add_widget: function(widget) {
+ add_widget: function(widget, colspan) {
+ colspan = colspan || widget.colspan;
var current_row = this.table[this.table.length - 1];
- if (current_row.length && (this.x + widget.colspan) > this.columns) {
+ if (current_row.length && (this.x + colspan) > this.columns) {
current_row = this.add_row();
}
current_row.push(widget);
@@ -610,14 +664,14 @@ openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
});
openerp.base.form.WidgetNotebook = openerp.base.form.Widget.extend({
+ template: 'WidgetNotebook',
init: function(view, node) {
this._super(view, node);
- this.template = "WidgetNotebook";
this.pages = [];
for (var i = 0; i < node.children.length; i++) {
var n = node.children[i];
if (n.tag == "page") {
- var page = new openerp.base.form.WidgetFrame(this.view, n);
+ var page = new openerp.base.form.WidgetNotebookPage(this.view, n, this, this.pages.length);
this.pages.push(page);
}
}
@@ -628,6 +682,28 @@ openerp.base.form.WidgetNotebook = openerp.base.form.Widget.extend({
}
});
+openerp.base.form.WidgetNotebookPage = openerp.base.form.WidgetFrame.extend({
+ template: 'WidgetNotebookPage',
+ init: function(view, node, notebook, index) {
+ this.notebook = notebook;
+ this.index = index;
+ this.element_name = 'page_' + index;
+ this._super(view, node);
+ this.element_tab_id = this.element_id + '_tab';
+ },
+ start: function() {
+ this._super.apply(this, arguments);
+ this.$element_tab = $('#' + this.element_tab_id);
+ },
+ update_dom: function() {
+ if (this.invisible) {
+ this.notebook.$element.tabs('select', 0);
+ }
+ this.$element_tab.toggle(!this.invisible);
+ this.$element.toggle(!this.invisible);
+ }
+});
+
openerp.base.form.WidgetSeparator = openerp.base.form.Widget.extend({
init: function(view, node) {
this._super(view, node);
@@ -639,6 +715,10 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
init: function(view, node) {
this._super(view, node);
this.template = "WidgetButton";
+ if (this.string) {
+ // We don't have button key bindings in the webclient
+ this.string = this.string.replace(/_/g, '');
+ }
if (node.attrs.default_focus == '1') {
// TODO fme: provide enter key binding to widgets
this.view.default_focus_button = this;
@@ -650,7 +730,7 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
},
on_click: function(saved) {
var self = this;
- if (!this.node.attrs.special && this.view.touched && saved !== true) {
+ if (!this.node.attrs.special && this.view.dirty && saved !== true) {
this.view.do_save(function() {
self.on_click(true);
});
@@ -679,10 +759,7 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
this.view.execute_action(
this.node.attrs, this.view.dataset, this.session.action_manager,
- this.view.datarecord.id, function (result) {
- self.log("Button returned", result);
- self.view.reload();
- }, function() {
+ this.view.datarecord.id, function () {
self.view.reload();
});
}
@@ -698,11 +775,12 @@ openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({
if (this.node.tag == 'label' && this.node.attrs.colspan) {
this.is_field_label = false;
this.template = "WidgetParagraph";
+ this.colspan = this.node.attrs.colspan;
} else {
this.is_field_label = true;
this.template = "WidgetLabel";
+ this.colspan = 1;
}
- this.colspan = 1;
},
render: function () {
if (this['for'] && this.type !== 'label') {
@@ -729,12 +807,11 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({
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.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.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1';
+ this.readonly = this.modifiers['readonly'] === true;
+ this.required = this.modifiers['required'] === true;
this.invalid = false;
- this.touched = false;
+ this.dirty = false;
},
set_value: function(value) {
this.value = value;
@@ -747,21 +824,29 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({
get_value: function() {
return this.value;
},
+ is_valid: function() {
+ return !this.invalid;
+ },
+ is_dirty: function() {
+ return this.dirty;
+ },
get_on_change_value: function() {
return this.get_value();
},
update_dom: function() {
this._super.apply(this, arguments);
- this.$element.toggleClass('disabled', this.readonly);
- this.$element.toggleClass('required', this.required);
- if (this.view.show_invalid) {
- this.$element.toggleClass('invalid', this.invalid);
+ if (!this.disable_utility_classes) {
+ this.$element.toggleClass('disabled', this.readonly);
+ this.$element.toggleClass('required', this.required);
+ if (this.view.show_invalid) {
+ this.$element.toggleClass('invalid', !this.is_valid());
+ }
}
},
on_ui_change: function() {
- this.touched = this.view.touched = true;
+ this.dirty = this.view.dirty = true;
this.validate();
- if (!this.invalid) {
+ if (this.is_valid()) {
this.set_value_from_ui();
this.view.do_onchange(this);
this.view.on_form_changed();
@@ -773,6 +858,43 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({
this.invalid = false;
},
focus: function() {
+ },
+ _build_view_fields_values: function() {
+ var a_dataset = this.view.dataset || {};
+ var fields_values = this.view.get_fields_values();
+ var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {};
+ fields_values.parent = parent_values;
+ return fields_values;
+ },
+ /**
+ * Builds a new context usable for operations related to fields by merging
+ * the fields'context with the action's context.
+ */
+ build_context: function() {
+ // I previously belevied contexts should be herrited, but now I doubt it
+ //var a_context = this.view.dataset.get_context() || {};
+ var f_context = this.field.context || null;
+ // maybe the default_get should only be used when we do a default_get?
+ var v_context1 = this.node.attrs.default_get || {};
+ var v_context2 = this.node.attrs.context || {};
+ var v_context = new openerp.base.CompoundContext(v_context1, v_context2);
+ if (v_context1.__ref || v_context2.__ref || true) { //TODO niv: remove || true
+ var fields_values = this._build_view_fields_values();
+ v_context.set_eval_context(fields_values);
+ }
+ // if there is a context on the node, overrides the model's context
+ var ctx = f_context || v_context;
+ return ctx;
+ },
+ build_domain: function() {
+ var f_domain = this.field.domain || null;
+ var v_domain = this.node.attrs.domain || [];
+ if (!(v_domain instanceof Array) || true) { //TODO niv: remove || true
+ var fields_values = this._build_view_fields_values();
+ v_domain = new openerp.base.CompoundDomain(v_domain).set_eval_context(fields_values);
+ }
+ // if there is a domain on the node, overrides the model's domain
+ return f_domain || v_domain;
}
});
@@ -822,7 +944,7 @@ openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({
this.$element.find('button').click(this.on_button_clicked);
},
on_button_clicked: function() {
- if (!this.value || this.invalid) {
+ if (!this.value || !this.is_valid()) {
this.notification.warn("E-mail error", "Can't send email to invalid e-mail address");
} else {
location.href = 'mailto:' + this.value;
@@ -863,6 +985,7 @@ openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
if (value === false || value === undefined) {
// As in GTK client, floats default to 0
value = 0;
+ this.dirty = true;
}
var show_value = value.toFixed(2);
this.$element.find('input').val(show_value);
@@ -872,22 +995,47 @@ openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
}
});
+openerp.base.form.FieldInteger = openerp.base.form.FieldFloat.extend({
+ init: function(view, node) {
+ this._super(view, node);
+ this.validation_regex = /^-?\d+$/;
+ },
+ set_value: function(value) {
+ this._super.apply(this, [value]);
+ if (value === false || value === undefined) {
+ // TODO fme: check if GTK client default integers to 0 (like it does with floats)
+ value = 0;
+ this.dirty = true;
+ }
+ var show_value = parseInt(value, 10);
+ this.$element.find('input').val(show_value);
+ },
+ set_value_from_ui: function() {
+ this.value = Number(this.$element.find('input').val());
+ }
+});
+
openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldDate";
this.jqueryui_object = 'datetimepicker';
+ this.validation_regex = /^\d+-\d+-\d+( \d+:\d+(:\d+)?)?$/;
},
start: function() {
this._super.apply(this, arguments);
this.$element.find('input').change(this.on_ui_change)[this.jqueryui_object]({
dateFormat: 'yy-mm-dd',
- timeFormat: 'hh:mm:ss'
+ timeFormat: 'hh:mm:ss',
+ showOn: 'button',
+ buttonImage: '/base/static/src/img/ui/field_calendar.png',
+ buttonImageOnly: true,
+ constrainInput: false
});
},
set_value: function(value) {
this._super.apply(this, arguments);
- if (value == null || value == false) {
+ if (!value) {
this.$element.find('input').val('');
} else {
this.$element.find('input').unbind('change');
@@ -902,8 +1050,20 @@ openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({
this.value = this.format(this.value);
}
},
+ update_dom: function() {
+ this._super.apply(this, arguments);
+ this.$element.find('input').datepicker(this.readonly ? 'disable' : 'enable');
+ },
validate: function() {
- this.invalid = this.required && !this.$element.find('input')[this.jqueryui_object]('getDate');
+ this.invalid = false;
+ var value = this.$element.find('input').val();
+ if (value === "") {
+ this.invalid = this.required;
+ } else if (this.validation_regex) {
+ this.invalid = !this.validation_regex.test(value);
+ } else {
+ this.invalid = !this.$element.find('input')[this.jqueryui_object]('getDate');
+ }
},
focus: function() {
this.$element.find('input').focus();
@@ -916,6 +1076,7 @@ openerp.base.form.FieldDate = openerp.base.form.FieldDatetime.extend({
init: function(view, node) {
this._super(view, node);
this.jqueryui_object = 'datepicker';
+ this.validation_regex = /^\d+-\d+-\d+$/;
},
parse: openerp.base.parse_date,
format: openerp.base.format_date
@@ -931,6 +1092,7 @@ openerp.base.form.FieldFloatTime = openerp.base.form.FieldChar.extend({
if (value === false || value === undefined) {
// As in GTK client, floats default to 0
value = 0;
+ this.dirty = true;
}
var show_value = _.sprintf("%02d:%02d", Math.floor(value), Math.round((value % 1) * 60));
this.$element.find('input').val(show_value);
@@ -1025,7 +1187,7 @@ openerp.base.form.FieldProgressBar = openerp.base.form.Field.extend({
set_value: function(value) {
this._super.apply(this, arguments);
var show_value = Number(value);
- if (show_value === NaN) {
+ if (isNaN(show_value)) {
show_value = 0;
}
this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(show_value + '%');
@@ -1040,28 +1202,54 @@ openerp.base.form.FieldSelection = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldSelection";
+ this.field_index = _.map(this.field.selection, function(x, index) {
+ return {"ikey": "" + index, "ekey": x[0], "label": x[1]};
+ });
},
start: function() {
+ // Flag indicating whether we're in an event chain containing a change
+ // event on the select, in order to know what to do on keyup[RETURN]:
+ // * If the user presses [RETURN] as part of changing the value of a
+ // selection, we should just let the value change and not let the
+ // event broadcast further (e.g. to validating the current state of
+ // the form in editable list view, which would lead to saving the
+ // current row or switching to the next one)
+ // * If the user presses [RETURN] with a select closed (side-effect:
+ // also if the user opened the select and pressed [RETURN] without
+ // changing the selected value), takes the action as validating the
+ // row
+ var ischanging = false;
this._super.apply(this, arguments);
- this.$element.find('select').change(this.on_ui_change);
+ this.$element.find('select')
+ .change(this.on_ui_change)
+ .change(function () { ischanging = true; })
+ .click(function () { ischanging = false; })
+ .keyup(function (e) {
+ if (e.which !== 13 || !ischanging) { return; }
+ e.stopPropagation();
+ ischanging = false;
+ });
},
set_value: function(value) {
- this._super.apply(this, arguments);
- if (value != null && value !== false) {
- this.$element.find('select').val(value);
- } else {
- this.$element.find('select').val('false');
- }
+ value = value === null ? false : value;
+ value = value instanceof Array ? value[0] : value;
+ this._super(value);
+ var option = _.detect(this.field_index, function(x) {return x.ekey === value;});
+ this.$element.find('select').val(option === undefined ? '' : option.ikey);
},
set_value_from_ui: function() {
- this.value = this.$element.find('select').val();
+ var ikey = this.$element.find('select').val();
+ var option = _.detect(this.field_index, function(x) {return x.ikey === ikey;});
+ this.value = option === undefined ? false : option.ekey;
},
update_dom: function() {
this._super.apply(this, arguments);
this.$element.find('select').attr('disabled', this.readonly);
},
validate: function() {
- this.invalid = this.required && this.$element.find('select').val() === "";
+ var ikey = this.$element.find('select').val();
+ var option = _.detect(this.field_index, function(x) {return x.ikey === ikey;});
+ this.invalid = this.required && (option === undefined || option.ekey === false);
},
focus: function() {
this.$element.find('select').focus();
@@ -1100,28 +1288,18 @@ openerp.base.form.FieldSelection = openerp.base.form.Field.extend({
});
})();
-var build_view_fields_values = function(view) {
- var a_dataset = view.dataset || {};
- var fields_values = view.get_fields_values();
- var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {};
- fields_values.parent = parent_values;
- return fields_values;
-}
-/**
- * Builds a new context usable for operations related to fields by merging
- * the fields'context with the action's context.
- */
-var build_relation_context = function(relation_field) {
- var a_context = relation_field.view.dataset.get_context() || {};
- var fields_values = build_view_fields_values(relation_field.view);
- var f_context = new openerp.base.CompoundContext(relation_field.field.context || {}).set_eval_context(fields_values);
- var ctx = new openerp.base.CompoundContext(a_context, f_context);
- return ctx;
-}
-var build_relation_domain = function(relation_field) {
- var fields_values = build_view_fields_values(relation_field.view);
- var f_domain = new openerp.base.CompoundDomain(relation_field.field.domain || []).set_eval_context(fields_values);
- return f_domain;
+openerp.base.form.dialog = function(content, options) {
+ options = _.extend({
+ autoOpen: true,
+ width: '90%',
+ height: '90%',
+ min_width: '800px',
+ min_height: '600px'
+ }, options || {});
+ options.autoOpen = true;
+ var dialog = new openerp.base.Dialog(null, options);
+ dialog.$dialog = $(content).dialog(dialog.options);
+ return dialog.$dialog;
}
openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
@@ -1153,15 +1331,10 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
if (!self.value) {
return;
}
- self.session.action_manager.do_action({
- "res_model": self.field.relation,
- "views":[[false,"form"]],
- "res_id": self.value[0],
- "type":"ir.actions.act_window",
- "view_type":"form",
- "view_mode":"form",
- "target":"new",
- "context": build_relation_context(self)
+ var pop = new openerp.base.form.FormOpenPopup(self.view);
+ pop.show_element(self.field.relation, self.value[0],self.build_context(), {});
+ pop.on_write_completed.add_last(function() {
+ self.set_value(self.value[0]);
});
};
var cmenu = this.$menu_btn.contextMenu(this.cm_id, {'leftClickToo': true,
@@ -1209,10 +1382,12 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
}
this.$input.focusout(anyoneLoosesFocus);
+ var isSelecting = false;
// autocomplete
this.$input.autocomplete({
source: function(req, resp) { self.get_search_result(req, resp); },
select: function(event, ui) {
+ isSelecting = true;
var item = ui.item;
if (item.id) {
self._change_int_value([item.id, item.name]);
@@ -1230,19 +1405,27 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
minLength: 0,
delay: 0
});
+ // used to correct a bug when selecting an element by pushing 'enter' in an editable list
+ this.$input.keyup(function(e) {
+ if (e.which === 13) {
+ if (isSelecting)
+ e.stopPropagation();
+ }
+ isSelecting = false;
+ });
},
// autocomplete component content handling
get_search_result: function(request, response) {
var search_val = request.term;
var self = this;
- var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, build_relation_context(self));
+ var dataset = new openerp.base.DataSetStatic(this, this.field.relation, self.build_context());
- dataset.name_search(search_val, build_relation_domain(self), 'ilike',
+ dataset.name_search(search_val, self.build_domain(), 'ilike',
this.limit + 1, function(data) {
- self.last_search = data.result;
+ self.last_search = data;
// possible selections for the m2o
- var values = _.map(data.result, function(x) {
+ var values = _.map(data, function(x) {
return {label: $('<span />').text(x[1]).html(), name:x[1], id:x[0]};
});
@@ -1250,10 +1433,10 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
if (values.length > self.limit) {
values = values.slice(0, self.limit);
values.push({label: "<em>   Search More...</em>", action: function() {
- dataset.name_search(search_val, build_relation_domain(self), 'ilike'
+ dataset.name_search(search_val, self.build_domain(), 'ilike'
, false, function(data) {
self._change_int_value(null);
- self._search_create_popup("search", data.result);
+ self._search_create_popup("search", data);
});
}});
}
@@ -1270,7 +1453,7 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
// create...
values.push({label: "<em>   Create and Edit...</em>", action: function() {
self._change_int_value(null);
- self._search_create_popup("form");
+ self._search_create_popup("form", undefined, {"default_name": search_val});
}});
response(values);
@@ -1278,9 +1461,9 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
},
_quick_create: function(name) {
var self = this;
- var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, build_relation_context(self));
+ var dataset = new openerp.base.DataSetStatic(this, this.field.relation, self.build_context());
dataset.name_create(name, function(data) {
- self._change_int_ext_value(data.result);
+ self._change_int_ext_value(data);
}).fail(function(error, event) {
event.preventDefault();
self._change_int_value(null);
@@ -1290,17 +1473,17 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
// all search/create popup handling
_search_create_popup: function(view, ids, context) {
var self = this;
- var pop = new openerp.base.form.SelectCreatePopup(null, self.view.session);
+ var pop = new openerp.base.form.SelectCreatePopup(this);
pop.select_element(self.field.relation,{
initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined,
initial_view: view,
disable_multiple_selection: true
- }, build_relation_domain(self),
- new openerp.base.CompoundContext(build_relation_context(self), context || {}));
+ }, self.build_domain(),
+ new openerp.base.CompoundContext(self.build_context(), context || {}));
pop.on_select_elements.add(function(element_ids) {
- var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, build_relation_context(self));
+ var dataset = new openerp.base.DataSetStatic(self, self.field.relation, self.build_context());
dataset.name_get([element_ids[0]], function(data) {
- self._change_int_ext_value(data.result[0]);
+ self._change_int_ext_value(data[0]);
pop.stop();
});
});
@@ -1336,9 +1519,9 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
self._change_int_ext_value(rval);
};
if(typeof(value) === "number") {
- var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, build_relation_context(self));
+ var dataset = new openerp.base.DataSetStatic(this, this.field.relation, self.build_context());
dataset.name_get([value], function(data) {
- real_set_value(data.result[0]);
+ real_set_value(data[0]);
}).fail(function() {self.tmp_value = undefined;});
} else {
setTimeout(function() {real_set_value(value);}, 0);
@@ -1409,17 +1592,20 @@ var commands = {
}
};
openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
+ multi_selection: false,
init: function(view, node) {
this._super(view, node);
this.template = "FieldOne2Many";
this.is_started = $.Deferred();
+ this.form_last_update = $.Deferred();
+ this.disable_utility_classes = true;
},
start: function() {
this._super.apply(this, arguments);
var self = this;
- this.dataset = new openerp.base.form.One2ManyDataSet(this.session, this.field.relation);
+ this.dataset = new openerp.base.form.One2ManyDataSet(this, this.field.relation);
this.dataset.o2m = this;
this.dataset.parent_view = this.view;
this.dataset.on_change.add_last(function() {
@@ -1436,27 +1622,41 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
}
if(view.view_type === "list") {
view.options = {
+ 'selectable': self.multi_selection
};
}
views.push(view);
});
this.views = views;
- this.viewmanager = new openerp.base.ViewManager(this.view.session,
- this.element_id, this.dataset, views);
+ this.viewmanager = new openerp.base.ViewManager(this, this.element_id, this.dataset, views);
this.viewmanager.registry = openerp.base.views.clone({
- list: 'openerp.base.form.One2ManyListView'
+ list: 'openerp.base.form.One2ManyListView',
+ form: 'openerp.base.form.One2ManyFormView'
+ });
+ var once = $.Deferred().then(function() {
+ self.form_last_update.resolve();
});
-
this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
if (view_type == "list") {
controller.o2m = self;
} else if (view_type == "form") {
- // TODO niv
+ controller.on_record_loaded.add_last(function() {
+ once.resolve();
+ });
+ controller.on_pager_action.add_first(function() {
+ self.save_form_view();
+ });
+ controller.$element.find(".oe_form_button_save_edit").hide();
}
self.is_started.resolve();
});
- this.viewmanager.start();
+ this.viewmanager.on_mode_switch.add_first(function() {
+ self.save_form_view();
+ });
+ setTimeout(function () {
+ self.viewmanager.start();
+ }, 0);
},
reload_current_view: function() {
var self = this;
@@ -1464,7 +1664,12 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
if(self.viewmanager.active_view === "list") {
view.reload_content();
} else if (self.viewmanager.active_view === "form") {
- // TODO niv: implement
+ if (this.dataset.index === null && this.dataset.ids.length >= 1) {
+ this.dataset.index = 0;
+ }
+ this.form_last_update.then(function() {
+ this.form_last_update = view.do_show();
+ });
}
},
set_value_from_ui: function() {},
@@ -1518,6 +1723,9 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
this._super(value);
this.dataset.reset_ids(value);
}
+ if (this.dataset.index === null && this.dataset.ids.length > 0) {
+ this.dataset.index = 0;
+ }
$.when(this.is_started).then(function() {
self.reload_current_view();
});
@@ -1542,47 +1750,97 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
this.dataset.to_delete, function(x) {
return commands['delete'](x.id);}));
},
- get_on_change_value: function() {
- var self;
- return _.map(this.dataset.ids, function(id) {
- var values = _.detect(self.dataset.cache, function(x) {return x.id === id});
- if (values) {
- values = _.clone(values);
- delete values["id"];
- return values;
- } else {
- console.info("trying to get value in a o2m before that value is loaded");
- return {};
+ save_form_view: function() {
+ if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view &&
+ this.viewmanager.views[this.viewmanager.active_view] &&
+ this.viewmanager.views[this.viewmanager.active_view].controller) {
+ var view = this.viewmanager.views[this.viewmanager.active_view].controller;
+ if (this.viewmanager.active_view === "form") {
+ var res = view.do_save();
+ if (res === false) {
+ // ignore
+ } else if (res.isRejected()) {
+ throw "Save or create on one2many dataset is not supposed to fail.";
+ } else if (!res.isResolved()) {
+ throw "Asynchronous get_value() is not supported in form view.";
+ }
+ return res;
}
- });
+ }
+ return false;
+ },
+ is_valid: function() {
+ this.validate();
+ return this._super();
},
validate: function() {
this.invalid = false;
- // TODO niv
+ var self = this;
+ var view = self.viewmanager.views[self.viewmanager.active_view].controller;
+ if (self.viewmanager.active_view === "form") {
+ for (var f in view.fields) {
+ f = view.fields[f];
+ if (!f.is_valid()) {
+ this.invalid = true;
+ return;
+ }
+ }
+ }
+ },
+ is_dirty: function() {
+ this.save_form_view();
+ return this._super();
+ },
+ update_dom: function() {
+ this._super.apply(this, arguments);
+ this.$element.toggleClass('disabled', this.readonly);
}
});
openerp.base.form.One2ManyDataSet = openerp.base.BufferedDataSet.extend({
get_context: function() {
- this.context = build_relation_context(this.o2m);
+ this.context = this.o2m.build_context();
return this.context;
}
});
+openerp.base.form.One2ManyFormView = openerp.base.FormView.extend({
+
+});
+
openerp.base.form.One2ManyListView = openerp.base.ListView.extend({
do_add_record: function () {
+ if (this.options.editable) {
+ this._super.apply(this, arguments);
+ } else {
+ var self = this;
+ var pop = new openerp.base.form.SelectCreatePopup(this);
+ pop.select_element(self.o2m.field.relation,{
+ initial_view: "form",
+ alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
+ auto_create: false,
+ parent_view: self.o2m.view
+ }, self.o2m.build_domain(), self.o2m.build_context());
+ pop.on_create.add(function(data) {
+ self.o2m.dataset.create(data, function(r) {
+ self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result]));
+ self.o2m.dataset.on_change();
+ pop.stop();
+ self.o2m.reload_current_view();
+ });
+ });
+ }
+ },
+ do_activate_record: function(index, id) {
var self = this;
- var pop = new openerp.base.form.SelectCreatePopup(null, self.o2m.view.session);
- pop.select_element(self.o2m.field.relation,{
- initial_view: "form",
+ var pop = new openerp.base.form.FormOpenPopup(self.o2m.view);
+ pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(),{
+ auto_write: false,
alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
- auto_create: false,
parent_view: self.o2m.view
- }, build_relation_domain(self.o2m), build_relation_context(self.o2m));
- pop.on_create.add(function(data) {
- self.o2m.dataset.create(data, function(r) {
- self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result]));
- pop.stop();
+ });
+ pop.on_write.add(function(id, data) {
+ self.o2m.dataset.write(id, data, function(r) {
self.o2m.reload_current_view();
});
});
@@ -1590,6 +1848,7 @@ openerp.base.form.One2ManyListView = openerp.base.ListView.extend({
});
openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
+ multi_selection: false,
init: function(view, node) {
this._super(view, node);
this.template = "FieldMany2Many";
@@ -1601,22 +1860,23 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
var self = this;
- this.dataset = new openerp.base.DataSetStatic(
- this.session, this.field.relation);
+ this.dataset = new openerp.base.form.Many2ManyDataSet(this, this.field.relation);
+ this.dataset.m2m = this;
this.dataset.on_unlink.add_last(function(ids) {
- //TODO niv: should check this for other cases
self.on_ui_change();
});
- this.list_view = new openerp.base.form.Many2ManyListView(
- null, this.view.session, this.list_id, this.dataset, false, {
- 'addable': 'Add'
+ this.list_view = new openerp.base.form.Many2ManyListView(new openerp.base.NullViewManager(this), this.list_id, this.dataset, false, {
+ 'addable': 'Add',
+ 'selectable': self.multi_selection
});
this.list_view.m2m_field = this;
this.list_view.on_loaded.add_last(function() {
self.is_started.resolve();
});
- this.list_view.start();
+ setTimeout(function () {
+ self.list_view.start();
+ }, 0);
},
set_value: function(value) {
value = value || [];
@@ -1632,19 +1892,33 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
},
get_value: function() {
return [commands.replace_with(this.dataset.ids)];
+ },
+ set_value_from_ui: function() {},
+ validate: function() {
+ this.invalid = false;
+ // TODO niv
+ }
+});
+
+openerp.base.form.Many2ManyDataSet = openerp.base.DataSetStatic.extend({
+ get_context: function() {
+ this.context = this.m2m.build_context();
+ return this.context;
}
});
openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
do_add_record: function () {
- var pop = new openerp.base.form.SelectCreatePopup(
- null, this.m2m_field.view.session);
- pop.select_element(this.model);
+ var pop = new openerp.base.form.SelectCreatePopup(this);
+ pop.select_element(this.model, {},
+ new openerp.base.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]),
+ this.m2m_field.build_context());
var self = this;
pop.on_select_elements.add(function(element_ids) {
_.each(element_ids, function(element_id) {
if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
self.dataset.set_ids([].concat(self.dataset.ids, [element_id]));
+ self.m2m_field.on_ui_change();
self.reload_content();
}
});
@@ -1652,19 +1926,16 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
});
},
do_activate_record: function(index, id) {
- this.m2m_field.view.session.action_manager.do_action({
- "res_model": this.dataset.model,
- "views":[[false,"form"]],
- "res_id": id,
- "type":"ir.actions.act_window",
- "view_type":"form",
- "view_mode":"form",
- "target":"new"
+ var self = this;
+ var pop = new openerp.base.form.FormOpenPopup(this);
+ pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {});
+ pop.on_write_completed.add_last(function() {
+ self.reload_content();
});
}
});
-openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({
+openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({
identifier_prefix: "selectcreatepopup",
template: "SelectCreatePopup",
/**
@@ -1682,15 +1953,13 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({
this.context = context || {};
this.options = _.defaults(options || {}, {"initial_view": "search", "auto_create": true});
this.initial_ids = this.options.initial_ids;
- jQuery(this.render()).dialog({title: '',
- modal: true,
- minWidth: 800});
+ openerp.base.form.dialog(this.render());
this.start();
},
start: function() {
this._super();
- this.dataset = new openerp.base.ReadOnlyDataSetSearch(this.session, this.model,
- this.context, this.domain);
+ this.dataset = new openerp.base.ReadOnlyDataSetSearch(this, this.model,
+ this.context);
this.dataset.parent_view = this.options.parent_view;
if (this.options.initial_view == "search") {
this.setup_search_view();
@@ -1703,18 +1972,18 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({
if (this.searchview) {
this.searchview.stop();
}
- this.searchview = new openerp.base.SearchView(null, this.session,
+ this.searchview = new openerp.base.SearchView(this,
this.element_id + "_search", this.dataset, false, {
"selectable": !this.options.disable_multiple_selection,
"deletable": false
});
this.searchview.on_search.add(function(domains, contexts, groupbys) {
if (self.initial_ids) {
- self.view_list.do_search.call(self, domains.concat([[["id", "in", self.initial_ids]]]),
+ self.view_list.do_search.call(self, domains.concat([[["id", "in", self.initial_ids]], self.domain]),
contexts, groupbys);
self.initial_ids = undefined;
} else {
- self.view_list.do_search.call(self, domains, contexts, groupbys);
+ self.view_list.do_search.call(self, domains.concat([self.domain]), contexts, groupbys);
}
});
this.searchview.on_loaded.add_last(function () {
@@ -1731,7 +2000,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({
$sbutton.click(function() {
self.on_select_elements(self.selected_ids);
});
- self.view_list = new openerp.base.form.SelectCreateListView( null, self.session,
+ self.view_list = new openerp.base.form.SelectCreateListView(self,
self.element_id + "_view_list", self.dataset, false,
{'deletable': false});
self.view_list.popup = self;
@@ -1746,8 +2015,8 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({
if (!this.options.auto_create)
return;
var self = this;
- var wdataset = new openerp.base.DataSetSearch(this.session, this.model, this.context, this.domain);
- wdataset = this.options.parent_view;
+ var wdataset = new openerp.base.DataSetSearch(this, this.model, this.context, this.domain);
+ wdataset.parent_view = this.options.parent_view;
wdataset.create(data, function(r) {
self.on_select_elements([r.result]);
});
@@ -1771,8 +2040,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({
this.view_list.$element.hide();
}
this.dataset.index = null;
- this.view_form = new openerp.base.FormView(null, this.session,
- this.element_id + "_view_form", this.dataset, false);
+ this.view_form = new openerp.base.FormView(this, this.element_id + "_view_form", this.dataset, false);
if (this.options.alternative_form_view) {
this.view_form.set_embedded_view(this.options.alternative_form_view);
}
@@ -1807,10 +2075,74 @@ openerp.base.form.SelectCreateListView = openerp.base.ListView.extend({
}
});
-openerp.base.form.FieldReference = openerp.base.form.Field.extend({
+openerp.base.form.FormOpenPopup = openerp.base.OldWidget.extend({
+ identifier_prefix: "formopenpopup",
+ template: "FormOpenPopup",
+ /**
+ * options:
+ * - alternative_form_view
+ * - auto_write (default true)
+ * - parent_view
+ */
+ show_element: function(model, row_id, context, options) {
+ this.model = model;
+ this.row_id = row_id;
+ this.context = context || {};
+ this.options = _.defaults(options || {}, {"auto_write": true});
+ jQuery(this.render()).dialog({title: '',
+ modal: true,
+ width: 960,
+ height: 600});
+ this.start();
+ },
+ start: function() {
+ this._super();
+ this.dataset = new openerp.base.ReadOnlyDataSetSearch(this, this.model, this.context);
+ this.dataset.ids = [this.row_id];
+ this.dataset.index = 0;
+ this.dataset.parent_view = this.options.parent_view;
+ this.setup_form_view();
+ },
+ on_write: function(id, data) {
+ this.stop();
+ if (!this.options.auto_write)
+ return;
+ var self = this;
+ var wdataset = new openerp.base.DataSetSearch(this, this.model, this.context, this.domain);
+ wdataset.parent_view = this.options.parent_view;
+ wdataset.write(id, data, function(r) {
+ self.on_write_completed();
+ });
+ },
+ on_write_completed: function() {},
+ setup_form_view: function() {
+ var self = this;
+ this.view_form = new openerp.base.FormView(this, this.element_id + "_view_form", this.dataset, false);
+ if (this.options.alternative_form_view) {
+ this.view_form.set_embedded_view(this.options.alternative_form_view);
+ }
+ this.view_form.start();
+ this.view_form.on_loaded.add_last(function() {
+ var $buttons = self.view_form.$element.find(".oe_form_buttons");
+ $buttons.html(QWeb.render("FormOpenPopup.form.buttons"));
+ var $nbutton = $buttons.find(".oe_formopenpopup-form-save");
+ $nbutton.click(function() {
+ self.view_form.do_save();
+ });
+ var $cbutton = $buttons.find(".oe_formopenpopup-form-close");
+ $cbutton.click(function() {
+ self.stop();
+ });
+ self.view_form.do_show();
+ });
+ this.dataset.on_write.add(this.on_write);
+ }
+});
+
+openerp.base.form.FieldReference = openerp.base.form.FieldChar.extend({
init: function(view, node) {
this._super(view, node);
- this.template = "FieldReference";
+ //this.template = "FieldReference";
}
});
@@ -1828,6 +2160,10 @@ openerp.base.form.FieldBinary = openerp.base.form.Field.extend({
},
set_value_from_ui: function() {
},
+ update_dom: function() {
+ this._super.apply(this, arguments);
+ this.$element.find('.oe-binary').toggle(!this.readonly);
+ },
human_filesize : function(size) {
var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var i = 0;
@@ -1925,6 +2261,13 @@ openerp.base.form.FieldBinaryImage = openerp.base.form.FieldBinary.extend({
this._super.apply(this, arguments);
this.$image = this.$element.find('img.oe-binary-image');
},
+ set_value: function(value) {
+ this._super.apply(this, arguments);
+ this.set_image_maxwidth();
+ var url = '/base/binary/image?session_id=' + this.session.session_id + '&model=' +
+ this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime())
+ this.$image.attr('src', url);
+ },
set_image_maxwidth: function() {
this.$image.css('max-width', this.$element.width());
},
@@ -1940,13 +2283,6 @@ openerp.base.form.FieldBinaryImage = openerp.base.form.FieldBinary.extend({
on_clear: function() {
this._super.apply(this, arguments);
this.$image.attr('src', '/base/static/src/img/placeholder.png');
- },
- set_value: function(value) {
- this._super.apply(this, arguments);
- this.set_image_maxwidth();
- var url = '/base/binary/image?session_id=' + this.session.session_id + '&model=' +
- this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime())
- this.$image.attr('src', url);
}
});
@@ -1965,8 +2301,10 @@ openerp.base.form.widgets = new openerp.base.Registry({
'url' : 'openerp.base.form.FieldUrl',
'text' : 'openerp.base.form.FieldText',
'text_wiki' : 'openerp.base.form.FieldText',
- 'date' : 'openerp.base.form.FieldDate',
- 'datetime' : 'openerp.base.form.FieldDatetime',
+// 'date' : 'openerp.base.form.FieldDate',
+// 'datetime' : 'openerp.base.form.FieldDatetime',
+ 'date' : 'openerp.base.form.FieldChar',
+ 'datetime' : 'openerp.base.form.FieldChar',
'selection' : 'openerp.base.form.FieldSelection',
'many2one' : 'openerp.base.form.FieldMany2One',
'many2many' : 'openerp.base.form.FieldMany2Many',
@@ -1975,7 +2313,7 @@ openerp.base.form.widgets = new openerp.base.Registry({
'reference' : 'openerp.base.form.FieldReference',
'boolean' : 'openerp.base.form.FieldBoolean',
'float' : 'openerp.base.form.FieldFloat',
- 'integer': 'openerp.base.form.FieldFloat',
+ 'integer': 'openerp.base.form.FieldInteger',
'progressbar': 'openerp.base.form.FieldProgressBar',
'float_time': 'openerp.base.form.FieldFloatTime',
'image': 'openerp.base.form.FieldBinaryImage',
diff --git a/addons/base/static/src/js/list-editable.js b/addons/base/static/src/js/list-editable.js
index b97a35d0612..9a48889d801 100644
--- a/addons/base/static/src/js/list-editable.js
+++ b/addons/base/static/src/js/list-editable.js
@@ -103,80 +103,106 @@ openerp.base.list.editable = function (openerp) {
* Checks if a record is being edited, and if so cancels it
*/
cancel_pending_edition: function () {
+ var self = this, cancelled = $.Deferred();
if (!this.edition) {
- return;
+ cancelled.resolve();
+ return cancelled.promise();
}
if (this.edition_index !== null) {
- this.reload_record(this.edition_index);
+ this.reload_record(this.edition_index, true).then(function () {
+ cancelled.resolve();
+ });
+ } else {
+ cancelled.resolve();
}
- this.edition_form.stop();
- this.edition_form.$element.remove();
- delete this.edition_form;
- delete this.edition_index;
- delete this.edition;
+ cancelled.then(function () {
+ self.edition_form.stop();
+ self.edition_form.$element.remove();
+ delete self.edition_form;
+ delete self.edition_index;
+ delete self.edition;
+ });
+ return cancelled.promise();
+ },
+ /**
+ * Adapts this list's view description to be suitable to the inner form view of a row being edited.
+ *
+ * @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows.
+ */
+ get_form_fields_view: function () {
+ // deep copy of view
+ var view = $.extend(true, {}, this.group.view.fields_view);
+ _(view.arch.children).each(function (widget) {
+ widget.attrs.nolabel = true;
+ if (widget.tag === 'button') {
+ delete widget.attrs.string;
+ }
+ });
+ view.arch.attrs.col = 2 * view.arch.children.length;
+ return view;
},
render_row_as_form: function (row) {
- this.cancel_pending_edition();
-
var self = this;
- var $new_row = $('<tr>', {
- id: _.uniqueId('oe-editable-row-'),
- 'class': $(row).attr('class'),
- click: function (e) {e.stopPropagation();}
- })
- .delegate('button.oe-edit-row-save', 'click', function () {
- self.save_row();
- })
- .delegate('button.oe-edit-row-cancel', 'click', function () {
- self.cancel_edition();
- })
- .delegate('button', 'keyup', function (e) {
- e.stopImmediatePropagation();
- })
- .keyup(function (e) {
- switch (e.which) {
- case KEY_RETURN:
- self.save_row(true);
- break;
- case KEY_ESCAPE:
- self.cancel_edition();
- break;
- default:
- return;
- }
- });
- if (row) {
- $new_row.replaceAll(row);
- } else if (this.options.editable === 'top') {
- this.$current.prepend($new_row);
- } else if (this.options.editable) {
- this.$current.append($new_row);
- }
- this.edition = true;
- this.edition_index = this.dataset.index;
- this.edition_form = _.extend(new openerp.base.FormView(
- null, this.group.view.session, $new_row.attr('id'),
- this.dataset, false), {
- template: 'ListView.row.form',
- registry: openerp.base.list.form.widgets
- });
- $.when(this.edition_form.on_loaded({fields_view: this.get_fields_view()})).then(function () {
- // put in $.when just in case FormView.on_loaded becomes asynchronous
- $new_row.find('td')
- .addClass('oe-field-cell')
- .removeAttr('width')
- .end()
- .find('td:first').removeClass('oe-field-cell').end()
- .find('td:last').removeClass('oe-field-cell').end();
- // pad in case of groupby
- _(self.columns).each(function (column) {
- if (column.meta) {
- $new_row.prepend('<td>');
- }
+ this.cancel_pending_edition().then(function () {
+ var $new_row = $('<tr>', {
+ id: _.uniqueId('oe-editable-row-'),
+ 'class': $(row).attr('class'),
+ click: function (e) {e.stopPropagation();}
+ })
+ .delegate('button.oe-edit-row-save', 'click', function () {
+ self.save_row();
+ })
+ .delegate('button.oe-edit-row-cancel', 'click', function () {
+ self.cancel_edition();
+ })
+ .delegate('button', 'keyup', function (e) {
+ e.stopImmediatePropagation();
+ })
+ .keyup(function (e) {
+ switch (e.which) {
+ case KEY_RETURN:
+ self.save_row(true);
+ break;
+ case KEY_ESCAPE:
+ self.cancel_edition();
+ break;
+ default:
+ return;
+ }
+ });
+ if (row) {
+ $new_row.replaceAll(row);
+ } else if (self.options.editable === 'top') {
+ self.$current.prepend($new_row);
+ } else if (self.options.editable) {
+ self.$current.append($new_row);
+ }
+ self.edition = true;
+ self.edition_index = self.dataset.index;
+ self.edition_form = _.extend(new openerp.base.FormView(
+ null, self.group.view.session, $new_row.attr('id'),
+ self.dataset, false), {
+ template: 'ListView.row.form',
+ registry: openerp.base.list.form.widgets
});
+ $.when(self.edition_form.on_loaded({fields_view: self.get_form_fields_view()})).then(function () {
+ // put in $.when just in case FormView.on_loaded becomes asynchronous
+ $new_row.find('td')
+ .addClass('oe-field-cell')
+ .removeAttr('width')
+ .end()
+ .find('td:first').removeClass('oe-field-cell').end()
+ .find('td:last').removeClass('oe-field-cell').end();
+ // pad in case of groupby
+ _(self.columns).each(function (column) {
+ if (column.meta) {
+ $new_row.prepend('<td>');
+ }
+ });
- self.edition_form.do_show();
+ self.edition_form.do_show();
+ });
});
},
/**
@@ -188,12 +214,7 @@ openerp.base.list.editable = function (openerp) {
save_row: function (edit_next) {
var self = this;
this.edition_form.do_save(function (result) {
- self.reload_record(self.dataset.index, true).then(function () {
- self.edition_form.stop();
- delete self.edition_form;
- delete self.edition_index;
- delete self.edition;
-
+ self.cancel_pending_edition().then(function () {
$(self).trigger('saved', [self.dataset]);
if (!edit_next) {
return;
@@ -230,7 +251,12 @@ openerp.base.list.editable = function (openerp) {
this.render_row_as_form();
}
});
- openerp.base.list = {form: {}};
+ if (!openerp.base.list) {
+ openerp.base.list = {};
+ }
+ if (!openerp.base.list.form) {
+ openerp.base.list.form = {};
+ }
openerp.base.list.form.WidgetFrame = openerp.base.form.WidgetFrame.extend({
template: 'ListView.row.frame'
});
@@ -251,10 +277,12 @@ openerp.base.list.editable = function (openerp) {
openerp.base.list.form[key] = (form_widgets.get_object(key)).extend({
update_dom: function () {
this.$element.children().css('visibility', '');
- if (this.invisible && this.node.attrs.invisible !== '1') {
+ if (this.invisible) {
this.$element.children().css('visibility', 'hidden');
} else {
+ this.invisible = !!this.modifiers.tree_invisible;
this._super();
+ this.invisible = false;
}
}
});
diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js
index 62bedf8784d..d64d83329c0 100644
--- a/addons/base/static/src/js/list.js
+++ b/addons/base/static/src/js/list.js
@@ -1,5 +1,63 @@
openerp.base.list = function (openerp) {
openerp.base.views.add('list', 'openerp.base.ListView');
+openerp.base.list = {
+ /**
+ * Formats the rendring of a given value based on its field type
+ *
+ * @param {Object} row_data record whose values should be displayed in the cell
+ * @param {Object} column column descriptor
+ * @param {"button"|"field"} column.tag base control type
+ * @param {String} column.type widget type for a field control
+ * @param {String} [column.string] button label
+ * @param {String} [column.icon] button icon
+ * @param {String} [value_if_empty=''] what to display if the field's value is ``false``
+ */
+ render_cell: function (row_data, column, value_if_empty) {
+ var attrs = column.modifiers_for(row_data);
+ if (attrs.invisible) { return ''; }
+ if (column.tag === 'button') {
+ return [
+ '<button type="button" title="', column.string || '', '">',
+ '<img src="/base/static/src/img/icons/', column.icon, '.png"',
+ ' alt="', column.string || '', '"/>',
+ '</button>'
+ ].join('')
+ }
+
+ var value = row_data[column.id].value;
+
+ // If NaN value, display as with a `false` (empty cell)
+ if (typeof value === 'number' && isNaN(value)) {
+ value = false;
+ }
+ switch (value) {
+ case false:
+ case Infinity:
+ case -Infinity:
+ return value_if_empty === undefined ? '' : value_if_empty;
+ }
+ switch (column.widget || column.type) {
+ case 'integer':
+ return _.sprintf('%d', value);
+ case 'float':
+ var precision = column.digits ? column.digits[1] : 2;
+ return _.sprintf('%.' + precision + 'f', value);
+ case 'float_time':
+ return _.sprintf("%02d:%02d",
+ Math.floor(value),
+ Math.round((value % 1) * 60));
+ case 'progressbar':
+ return _.sprintf(
+ '<progress value="%.2f" max="100.0">%.2f%%</progress>',
+ value, value);
+ case 'many2one':
+ // name_get value format
+ return value[1];
+ default:
+ return value;
+ }
+ }
+};
openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListView# */ {
defaults: {
// records can be selected one by one
@@ -42,9 +100,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
*
* @borrows openerp.base.ActionExecutor#execute_action as #execute_action
*/
- init: function(view_manager, session, element_id, dataset, view_id, options) {
- this._super(session, element_id);
- this.view_manager = view_manager || new openerp.base.NullViewManager();
+ init: function(parent, element_id, dataset, view_id, options) {
+ this._super(parent, element_id);
+ this.set_default_options();
+ this.view_manager = parent || new openerp.base.NullViewManager();
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
@@ -52,13 +111,30 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
this.columns = [];
this.options = _.extend({}, this.defaults, options || {});
- this.flags = this.view_manager.flags || {};
this.set_groups(new openerp.base.ListView.Groups(this));
-
+
if (this.dataset instanceof openerp.base.DataSetStatic) {
this.groups.datagroup = new openerp.base.StaticDataGroup(this.dataset);
}
+
+ this.page = 0;
+ },
+ /**
+ * Retrieves the view's number of records per page (|| section)
+ *
+ * options > defaults > view_manager.action.limit > indefinite
+ *
+ * @returns {Number|null}
+ */
+ limit: function () {
+ if (this._limit === undefined) {
+ this._limit = (this.options.limit
+ || this.defaults.limit
+ || (this.view_manager.action || {}).limit
+ || null);
+ }
+ return this._limit;
},
/**
* Set a custom Group construct as the root of the List View.
@@ -129,27 +205,103 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
this.setup_columns(this.fields_view.fields, grouped);
- if (!this.fields_view.sorted) { this.fields_view.sorted = {}; }
-
this.$element.html(QWeb.render("ListView", this));
// Head hook
- this.$element.find('#oe-list-add')
+ this.$element.find('.oe-list-add')
.click(this.do_add_record)
.attr('disabled', grouped && this.options.editable);
- this.$element.find('#oe-list-delete')
+ this.$element.find('.oe-list-delete')
.attr('disabled', true)
.click(this.do_delete_selected);
- this.$element.find('thead').delegate('th[data-id]', 'click', function (e) {
+ this.$element.find('thead').delegate('th.oe-sortable[data-id]', 'click', function (e) {
e.stopPropagation();
- self.dataset.sort($(this).data('id'));
+ var $this = $(this);
+ self.dataset.sort($this.data('id'));
+ if ($this.find('span').length) {
+ $this.find('span').toggleClass(
+ 'ui-icon-triangle-1-s ui-icon-triangle-1-n');
+ } else {
+ $this.append('<span class="ui-icon ui-icon-triangle-1-s">')
+ .siblings('.oe-sortable').find('span').remove();
+ }
- // TODO: should only reload content (and set the right column to a sorted display state)
- self.reload_view();
+ self.reload_content();
});
- this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
+ this.$element.find('.oe-list-pager')
+ .delegate('button', 'click', function () {
+ var $this = $(this);
+ switch ($this.data('pager-action')) {
+ case 'first':
+ self.page = 0; break;
+ case 'last':
+ self.page = Math.floor(
+ self.dataset.ids.length / self.limit());
+ break;
+ case 'next':
+ self.page += 1; break;
+ case 'previous':
+ self.page -= 1; break;
+ }
+ self.reload_content();
+ }).find('.oe-pager-state')
+ .click(function (e) {
+ e.stopPropagation();
+ var $this = $(this);
+
+ var $select = $('<select>')
+ .appendTo($this.empty())
+ .click(function (e) {e.stopPropagation();})
+ .append('<option value="80">80</option>' +
+ '<option value="100">100</option>' +
+ '<option value="200">200</option>' +
+ '<option value="500">500</option>' +
+ '<option value="NaN">Unlimited</option>')
+ .change(function () {
+ var val = parseInt($select.val(), 10);
+ self._limit = (isNaN(val) ? null : val);
+ self.page = 0;
+ self.reload_content();
+ })
+ .val(self._limit || 'NaN');
+ });
+ if (this.options.sidebar && this.options.sidebar_id) {
+ this.sidebar = new openerp.base.Sidebar(this, this.options.sidebar_id);
+ this.sidebar.start();
+ this.sidebar.add_toolbar(data.fields_view.toolbar);
+ this.set_common_sidebar_sections(this.sidebar);
+ }
+ },
+ /**
+ * Configures the ListView pager based on the provided dataset's information
+ *
+ * Horrifying side-effect: sets the dataset's data on this.dataset?
+ *
+ * @param {openerp.base.DataSet} dataset
+ */
+ configure_pager: function (dataset) {
+ this.dataset.ids = dataset.ids;
+
+ var limit = this.limit(),
+ total = dataset.ids.length,
+ first = (this.page * limit),
+ last;
+ if (!limit || (total - first) < limit) {
+ last = total;
+ } else {
+ last = first + limit;
+ }
+ this.$element.find('span.oe-pager-state').empty().text(_.sprintf(
+ "[%d to %d] of %d", first + 1, last, total));
+
+ this.$element
+ .find('button[data-pager-action=first], button[data-pager-action=previous]')
+ .attr('disabled', this.page === 0)
+ .end()
+ .find('button[data-pager-action=last], button[data-pager-action=next]')
+ .attr('disabled', last === total);
},
/**
* Sets up the listview's columns: merges view and fields data, move
@@ -167,22 +319,26 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
var name = field.attrs.name;
var column = _.extend({id: name, tag: field.tag},
field.attrs, fields[name]);
- // attrs computer
- if (column.attrs) {
- var attrs = JSON.parse(column.attrs);
- column.attrs_for = function (fields) {
- var result = {};
- for (var attr in attrs) {
- result[attr] = domain_computer(attrs[attr], fields);
+ // modifiers computer
+ if (column.modifiers) {
+ var modifiers = JSON.parse(column.modifiers);
+ column.modifiers_for = function (fields) {
+ if (!modifiers.invisible) {
+ return {};
}
- return result;
+ return {
+ 'invisible': domain_computer(modifiers.invisible, fields)
+ };
};
+ if (modifiers['tree_invisible']) {
+ column.invisible = '1';
+ }
} else {
- column.attrs_for = noop;
+ column.modifiers_for = noop;
}
return column;
};
-
+
this.columns.splice(0, this.columns.length);
this.columns.push.apply(
this.columns,
@@ -190,10 +346,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
if (grouped) {
this.columns.unshift({
id: '_group', tag: '', string: "Group", meta: true,
- attrs_for: function () { return {}; }
+ modifiers_for: function () { return {}; }
}, {
id: '_count', tag: '', string: '#', meta: true,
- attrs_for: function () { return {}; }
+ modifiers_for: function () { return {}; }
});
}
@@ -208,16 +364,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
}
var aggregation_func = column['group_operator'] || 'sum';
- if (!column[aggregation_func]) {
- return {};
- }
-
- return {
- field: column.id,
- type: column.type,
+ return _.extend({}, column, {
'function': aggregation_func,
label: column[aggregation_func]
- };
+ });
});
},
/**
@@ -246,15 +396,20 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
},
do_show: function () {
this.$element.show();
+ if (this.sidebar) {
+ this.sidebar.$element.show();
+ }
if (this.hidden) {
- this.$element.find('table').append(
+ this.$element.find('.oe-listview-content').append(
this.groups.apoptosis().render());
this.hidden = false;
}
- this.view_manager.sidebar.do_refresh(true);
},
do_hide: function () {
this.$element.hide();
+ if (this.sidebar) {
+ this.sidebar.$element.hide();
+ }
this.hidden = true;
},
/**
@@ -264,10 +419,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
*/
reload_view: function (grouped) {
var self = this;
- this.dataset.offset = 0;
- this.dataset.limit = false;
var callback = function (field_view_get) {
- self.on_loaded(field_view_get, grouped);
+ self.on_loaded(field_view_get, grouped);
};
if (this.embedded_view) {
return $.Deferred().then(callback).resolve({fields_view: this.embedded_view});
@@ -275,8 +428,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
return this.rpc('/base/listview/load', {
model: this.model,
view_id: this.view_id,
- context: this.dataset.context,
- toolbar: !!this.flags.sidebar
+ context: this.dataset.get_context(),
+ toolbar: this.options.sidebar
}, callback);
}
},
@@ -284,7 +437,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
* re-renders the content of the list view
*/
reload_content: function () {
- this.$element.find('table').append(
+ this.$element.find('.oe-listview-content').append(
this.groups.apoptosis().render(
$.proxy(this, 'compute_aggregates')));
},
@@ -314,9 +467,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
this.dataset.context = results.context;
this.dataset.domain = results.domain;
this.groups.datagroup = new openerp.base.DataGroup(
- this.session, this.model,
+ this, this.model,
results.domain, results.context,
results.group_by);
+ this.groups.datagroup.sort = this.dataset._sort;
if (_.isEmpty(results.group_by) && !results.context['group_by_no_leaf']) {
results.group_by = null;
@@ -326,9 +480,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
$.proxy(this, 'reload_content'));
},
/**
- * Handles the signal to delete a line from the DOM
+ * Handles the signal to delete lines from the records list
*
- * @param {Array} ids the id of the object to delete
+ * @param {Array} ids the ids of the records to delete
*/
do_delete: function (ids) {
if (!ids.length) {
@@ -336,7 +490,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
}
var self = this;
return $.when(this.dataset.unlink(ids)).then(function () {
- self.reload_content();
+ self.groups.drop_records(ids);
+ self.compute_aggregates();
});
},
/**
@@ -346,7 +501,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
* @param {Array} records selected record values
*/
do_select: function (ids, records) {
- this.$element.find('#oe-list-delete')
+ this.$element.find('.oe-list-delete')
.attr('disabled', !ids.length);
if (!records.length) {
@@ -365,16 +520,16 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
* @param {Function} callback should be called after the action is executed, if non-null
*/
do_action: function (name, id, callback) {
- var action = _.detect(this.columns, function (field) {
+ var self = this,
+ action = _.detect(this.columns, function (field) {
return field.name === name;
});
if (!action) { return; }
this.execute_action(
- action, this.dataset, this.session.action_manager,
- id, function () {
- if (callback) {
- callback();
- }
+ action, this.dataset, this.session.action_manager, id, function () {
+ $.when(callback.apply(this, arguments).then(function () {
+ self.compute_aggregates();
+ }));
});
},
/**
@@ -388,7 +543,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
var self = this;
_.extend(this.dataset, {
domain: dataset.domain,
- context: dataset.context
+ context: dataset.get_context()
}).read_slice([], 0, false, function () {
self.select_record(index);
});
@@ -422,7 +577,6 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
compute_aggregates: function (records) {
var columns = _(this.aggregate_columns).filter(function (column) {
return column['function']; });
-
if (_.isEmpty(columns)) { return; }
if (_.isEmpty(records)) {
@@ -430,17 +584,39 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
}
var count = 0, sums = {};
- _(columns).each(function (column) { sums[column.field] = 0; });
+ _(columns).each(function (column) {
+ switch (column['function']) {
+ case 'max':
+ sums[column.id] = -Infinity;
+ break;
+ case 'min':
+ sums[column.id] = Infinity;
+ break;
+ default:
+ sums[column.id] = 0;
+ }
+ });
_(records).each(function (record) {
count += record.count || 1;
_(columns).each(function (column) {
- var field = column.field;
+ var field = column.id,
+ value = record.values[field];
switch (column['function']) {
case 'sum':
- sums[field] += record.values[field];
+ sums[field] += value;
break;
case 'avg':
- sums[field] += record.count * record.values[field];
+ sums[field] += record.count * value;
+ break;
+ case 'min':
+ if (sums[field] > value) {
+ sums[field] = value;
+ }
+ break;
+ case 'max':
+ if (sums[field] < value) {
+ sums[field] = value;
+ }
break;
}
});
@@ -448,14 +624,13 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
var aggregates = {};
_(columns).each(function (column) {
- var field = column.field;
+ var field = column.id;
switch (column['function']) {
- case 'sum':
- aggregates[field] = sums[field];
- break;
case 'avg':
- aggregates[field] = sums[field] / count;
+ aggregates[field] = {value: sums[field] / count};
break;
+ default:
+ aggregates[field] = {value: sums[field]};
}
});
@@ -467,14 +642,14 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
if (!column['function']) {
return;
}
- var pattern = (column.type == 'integer') ? '%d' : '%.2f';
- $footer_cells.filter(_.sprintf('[data-field=%s]', column.field))
- .text(_.sprintf(pattern, aggregation[column.field]));
+
+ $footer_cells.filter(_.sprintf('[data-field=%s]', column.id))
+ .html(openerp.base.list.render_cell(aggregation, column));
});
}
// TODO: implement reorder (drag and drop rows)
});
-openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List# */{
+openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.ListView.List# */{
/**
* List display for the ListView, handles basic DOM events and transforms
* them in the relevant higher-level events, to which the list view (or
@@ -505,6 +680,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
init: function (group, opts) {
var self = this;
this.group = group;
+ this.view = group.view;
this.options = opts.options;
this.columns = opts.columns;
@@ -533,7 +709,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
index = self.row_position($row);
$(self).trigger('action', [field, record_id, function () {
- self.reload_record(index, true);
+ return self.reload_record(index, true);
}]);
})
.delegate('tr', 'click', function (e) {
@@ -553,19 +729,9 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
this.$current.remove();
}
this.$current = this.$_element.clone(true);
- this.$current.empty().append(QWeb.render('ListView.rows', this));
- },
- get_fields_view: function () {
- // deep copy of view
- var view = $.extend(true, {}, this.group.view.fields_view);
- _(view.arch.children).each(function (widget) {
- widget.attrs.nolabel = true;
- if (widget.tag === 'button') {
- delete widget.attrs.string;
- }
- });
- view.arch.attrs.col = 2 * view.arch.children.length;
- return view;
+ this.$current.empty().append(
+ QWeb.render('ListView.rows', _.extend({
+ render_cell: openerp.base.list.render_cell}, this)));
},
/**
* Gets the ids of all currently selected records, if any
@@ -681,7 +847,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
return $.when(read_p).then(function () {
self.$current.children().eq(record_index)
- .replaceWith(self.render_record(record_index)); })
+ .replaceWith(self.render_record(record_index)); });
},
/**
* Renders a list record to HTML
@@ -695,12 +861,31 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
options: this.options,
row: this.rows[record_index],
row_parity: (record_index % 2 === 0) ? 'even' : 'odd',
- row_index: record_index
+ row_index: record_index,
+ render_cell: openerp.base.list.render_cell
});
+ },
+ /**
+ * Stops displaying the records matching the provided ids.
+ *
+ * @param {Array} ids identifiers of the records to remove
+ */
+ drop_records: function (ids) {
+ var self = this;
+ _(this.rows).chain()
+ .map(function (record, index) {
+ return {index: index, id: record.data.id.value};
+ }).filter(function (record) {
+ return _(ids).contains(record.id);
+ }).reverse()
+ .each(function (record) {
+ self.$current.find('tr:eq(' + record.index + ')').remove();
+ self.rows.splice(record.index, 1);
+ })
}
// drag and drop
});
-openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Groups# */{
+openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.base.ListView.Groups# */{
passtrough_events: 'action deleted row_link',
/**
* Grouped display for the ListView. Handles basic DOM events and interacts
@@ -715,13 +900,10 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
this.columns = view.columns;
this.datagroup = null;
- this.sections = [];
+ this.$row = null;
this.children = {};
- },
- pad: function ($row) {
- if (this.options.selectable) {
- $row.append('<td>');
- }
+
+ this.page = 0;
},
make_fragment: function () {
return document.createDocumentFragment();
@@ -753,10 +935,35 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
}
return red_letter_tboday;
},
+ make_paginator: function () {
+ var self = this;
+ var $prev = $('<button type="button" data-pager-action="previous">&lt;</button>')
+ .click(function (e) {
+ e.stopPropagation();
+ self.page -= 1;
+
+ self.$row.closest('tbody').next()
+ .replaceWith(self.render());
+ });
+ var $next = $('<button type="button" data-pager-action="next">&gt;</button>')
+ .click(function (e) {
+ e.stopPropagation();
+ self.page += 1;
+
+ self.$row.closest('tbody').next()
+ .replaceWith(self.render());
+ });
+ this.$row.children().last()
+ .append($prev)
+ .append('<span class="oe-pager-state"></span>')
+ .append($next);
+ },
open: function (point_insertion) {
this.render().insertAfter(point_insertion);
+ this.make_paginator();
},
close: function () {
+ this.$row.children().last().empty();
this.apoptosis();
},
/**
@@ -788,7 +995,7 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
self.bind_child_events(child);
child.datagroup = group;
- var $row = $('<tr>');
+ var $row = child.$row = $('<tr>');
if (group.openable) {
$row.click(function (e) {
if (!$row.data('open')) {
@@ -808,11 +1015,16 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
}
placeholder.appendChild($row[0]);
- var $group_column = $('<th>').appendTo($row);
+ var $group_column = $('<th class="oe-group-name">').appendTo($row);
+ // Don't fill this if group_by_no_leaf but no group_by
if (group.grouped_on) {
- // Don't fill this if group_by_no_leaf but no group_by
- $group_column
- .text((group.value instanceof Array ? group.value[1] : group.value));
+ var row_data = {};
+ row_data[group.grouped_on] = group;
+ var group_column = _(self.columns).detect(function (column) {
+ return column.id === group.grouped_on; });
+ $group_column.html(openerp.base.list.render_cell(
+ row_data, group_column, "Undefined"
+ ));
if (group.openable) {
// Make openable if not terminal group & group_by_no_leaf
$group_column
@@ -822,8 +1034,10 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
self.indent($group_column, group.level);
// count column
$('<td>').text(group.length).appendTo($row);
-
- self.pad($row);
+
+ if (self.options.selectable) {
+ $row.append('<td>');
+ }
_(self.columns).chain()
.filter(function (column) {return !column.invisible;})
.each(function (column) {
@@ -844,6 +1058,9 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
$row.append('<td>');
}
});
+ if (self.options.deletable) {
+ $row.append('<td class="oe-group-pagination">');
+ }
});
return placeholder;
},
@@ -867,6 +1084,7 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
},
render_dataset: function (dataset) {
var rows = [],
+ self = this,
list = new openerp.base.ListView.List(this, {
options: this.options,
columns: this.columns,
@@ -875,11 +1093,30 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
});
this.bind_child_events(list);
- var d = new $.Deferred();
+ var view = this.view,
+ limit = view.limit(),
+ d = new $.Deferred(),
+ page = this.datagroup.openable ? this.page : view.page;
+
dataset.read_slice(
_.filter(_.pluck(_.select(this.columns, function(x) {return x.tag == "field";}), 'name'), _.identity),
- 0, false,
+ page * limit, limit,
function (records) {
+ if (!self.datagroup.openable) {
+ view.configure_pager(dataset);
+ } else {
+ var pages = Math.ceil(dataset.ids.length / limit);
+ self.$row
+ .find('.oe-pager-state')
+ .text(_.sprintf('%d/%d', page + 1, pages))
+ .end()
+ .find('button[data-pager-action=previous]')
+ .attr('disabled', page === 0)
+ .end()
+ .find('button[data-pager-action=next]')
+ .attr('disabled', page === pages - 1);
+ }
+
var form_records = _(records).map(
$.proxy(list, 'transform_record'));
@@ -890,22 +1127,67 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
});
return d.promise();
},
+ setup_resequence_rows: function (list, dataset) {
+ // drag and drop enabled if list is not sorted and there is a
+ // "sequence" column in the view.
+ if ((dataset.sort && dataset.sort())
+ || !_(this.columns).any(function (column) {
+ return column.name === 'sequence'; })) {
+ return;
+ }
+ // ondrop, move relevant record & fix sequences
+ list.$current.sortable({
+ stop: function (event, ui) {
+ var from = ui.item.data('index'),
+ to = ui.item.prev().data('index') || 0;
+ if (from === to) { return; }
+ list.rows.splice(to, 0, list.rows.splice(from, 1)[0]);
+
+ ui.item.parent().children().each(function (i, e) {
+ // reset record-index accelerators on rows and even/odd
+ var even = i%2 === 0;
+ $(e).data('index', i)
+ .toggleClass('even', even)
+ .toggleClass('odd', !even);
+ });
+
+ // resequencing time!
+ var data, index = to,
+ // if drag to 1st row (to = 0), start sequencing from 0
+ // (exclusive lower bound)
+ seq = to ? list.rows[to - 1].data.sequence.value : 0;
+ while (++seq, data = list.rows[index++].data) {
+ data.sequence.value = seq;
+ // write are independent from one another, so we can just
+ // launch them all at the same time and we don't really
+ // give a fig about when they're done
+ dataset.write(data.id.value, {sequence: seq});
+ }
+ }
+ });
+ },
render: function (post_render) {
var self = this;
var $element = $('<tbody>');
this.elements = [$element[0]];
- this.datagroup.list(function (groups) {
- $element[0].appendChild(
- self.render_groups(groups));
- if (post_render) { post_render(); }
- }, function (dataset) {
- self.render_dataset(dataset).then(function (list) {
- self.children[null] = list;
- self.elements =
- [list.$current.replaceAll($element)[0]];
+
+ this.datagroup.list(
+ _(this.view.visible_columns).chain()
+ .filter(function (column) { return column.tag === 'field' })
+ .pluck('name').value(),
+ function (groups) {
+ $element[0].appendChild(
+ self.render_groups(groups));
if (post_render) { post_render(); }
+ }, function (dataset) {
+ self.render_dataset(dataset).then(function (list) {
+ self.children[null] = list;
+ self.elements =
+ [list.$current.replaceAll($element)[0]];
+ self.setup_resequence_rows(list, dataset);
+ if (post_render) { post_render(); }
+ });
});
- });
return $element;
},
/**
@@ -943,6 +1225,19 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
.map(function (child) {
return child.get_records();
}).flatten().value();
+ },
+ /**
+ * Stops displaying the records with the linked ids, assumes these records
+ * were deleted from the DB.
+ *
+ * This is the up-signal from the `deleted` event on groups and lists.
+ *
+ * @param {Array} ids list of identifier of the records to remove.
+ */
+ drop_records: function (ids) {
+ _.each(this.children, function (child) {
+ child.drop_records(ids);
+ });
}
});
};
diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js
index 6efa6988fe8..763746bb0bf 100644
--- a/addons/base/static/src/js/search.js
+++ b/addons/base/static/src/js/search.js
@@ -1,9 +1,9 @@
openerp.base.search = function(openerp) {
-openerp.base.SearchView = openerp.base.Controller.extend({
- init: function(view_manager, session, element_id, dataset, view_id, defaults) {
- this._super(session, element_id);
- this.view_manager = view_manager || new openerp.base.NullViewManager();
+openerp.base.SearchView = openerp.base.Widget.extend({
+ init: function(parent, element_id, dataset, view_id, defaults) {
+ this._super(parent, element_id);
+ this.view_manager = parent || new openerp.base.NullViewManager();
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
@@ -14,10 +14,13 @@ openerp.base.SearchView = openerp.base.Controller.extend({
this.enabled_filters = [];
this.has_focus = false;
+
+ this.ready = $.Deferred();
},
start: function() {
//this.log('Starting SearchView '+this.model+this.view_id)
- return this.rpc("/base/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded);
+ this.rpc("/base/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded);
+ return this.ready.promise();
},
show: function () {
this.$element.show();
@@ -107,12 +110,13 @@ openerp.base.SearchView = openerp.base.Controller.extend({
}
},
on_loaded: function(data) {
- var lines = this.make_widgets(
- data.fields_view['arch'].children,
- data.fields_view.fields);
+ var self = this,
+ lines = this.make_widgets(
+ data.fields_view['arch'].children,
+ data.fields_view.fields);
// for extended search view
- var ext = new openerp.base.search.ExtendedSearch(null, this.session, this.model);
+ var ext = new openerp.base.search.ExtendedSearch(this, this.model);
lines.push([ext]);
this.inputs.push(ext);
@@ -121,6 +125,7 @@ openerp.base.SearchView = openerp.base.Controller.extend({
'lines': lines,
'defaults': this.defaults
});
+
// We don't understand why the following commented line does not work in Chrome but
// the non-commented line does. As far as we investigated, only God knows.
//this.$element.html(render);
@@ -132,30 +137,90 @@ openerp.base.SearchView = openerp.base.Controller.extend({
.submit(this.do_search)
.bind('reset', this.do_clear);
// start() all the widgets
- _(lines).chain().flatten().each(function (widget) {
- widget.start();
+ var widget_starts = _(lines).chain().flatten().map(function (widget) {
+ return widget.start();
+ }).value();
+
+ $.when.apply(null, widget_starts).then(function () {
+ self.ready.resolve();
});
- // filters management
- this.$element.find(".oe_search-view-filters-management").change(this.on_filters_management);
+ this.reload_managed_filters();
+ },
+ reload_managed_filters: function() {
+ var self = this;
+ return this.rpc('/base/searchview/get_filters', {
+ model: this.dataset.model
+ }).then(function(result) {
+ self.managed_filters = result;
+ var filters = self.$element.find(".oe_search-view-filters-management");
+ filters.html(QWeb.render("SearchView.managed-filters", {filters: result}));
+ filters.change(self.on_filters_management);
+ });
},
/**
* Handle event when the user make a selection in the filters management select box.
*/
on_filters_management: function(e) {
+ var self = this;
var select = this.$element.find(".oe_search-view-filters-management");
var val = select.val();
- select.val("_filters");
- if (val.slice(0,1) == "_") // useless action
+ if (val.slice(0,1) == "_") { // useless action
+ select.val("_filters");
return;
+ }
if (val.slice(0, "get:".length) == "get:") {
val = val.slice("get:".length);
- //TODO niv
+ val = parseInt(val);
+ var filter = this.managed_filters[val];
+ this.on_search([filter.domain], [filter.context], []);
} else if (val == "save_filter") {
- //TODO niv
+ select.val("_filters");
+ var data = this.build_search_data();
+ var context = new openerp.base.CompoundContext();
+ _.each(data.contexts, function(x) {
+ context.add(x);
+ });
+ var domain = new openerp.base.CompoundDomain();
+ _.each(data.domains, function(x) {
+ domain.add(x);
+ });
+ var dial_html = QWeb.render("SearchView.managed-filters.add");
+ var $dial = $(dial_html);
+ $dial.dialog({
+ modal: true,
+ title: "Filter Entry",
+ buttons: {
+ Cancel: function() {
+ $(this).dialog("close");
+ },
+ OK: function() {
+ $(this).dialog("close");
+ var name = $(this).find("input").val();
+ self.rpc('/base/searchview/save_filter', {
+ model: self.dataset.model,
+ context_to_save: context,
+ domain: domain,
+ name: name
+ }).then(function(result) {
+ self.reload_managed_filters();
+ });
+ },
+ }
+ });
} else { // manage_filters
- //TODO niv
+ select.val("_filters");
+ this.do_action({
+ res_model: 'ir.filters',
+ views: [[false, 'list'], [false, 'form']],
+ type: 'ir.actions.act_window',
+ context: {"search_default_user_id": this.session.uid,
+ "search_default_model_id": this.dataset.model},
+ target: "current",
+ limit : 80,
+ auto_search : true
+ });
}
},
/**
@@ -170,11 +235,25 @@ openerp.base.SearchView = openerp.base.Controller.extend({
* @param e jQuery event object coming from the "Search" button
*/
do_search: function (e) {
+ // reset filters management
+ var select = this.$element.find(".oe_search-view-filters-management");
+ select.val("_filters");
+
if (e && e.preventDefault) { e.preventDefault(); }
- var domains = [], contexts = [];
+ var data = this.build_search_data();
+
+ if (data.errors.length) {
+ this.on_invalid(data.errors);
+ return;
+ }
- var errors = [];
+ this.on_search(data.domains, data.contexts, data.groupbys);
+ },
+ build_search_data: function() {
+ var domains = [],
+ contexts = [],
+ errors = [];
_.each(this.inputs, function (input) {
try {
@@ -196,19 +275,13 @@ openerp.base.SearchView = openerp.base.Controller.extend({
}
});
- if (errors.length) {
- this.on_invalid(errors);
- return;
- }
-
// TODO: do we need to handle *fields* with group_by in their context?
var groupbys = _(this.enabled_filters)
.chain()
.map(function (filter) { return filter.get_context();})
.compact()
.value();
-
- this.on_search(domains, contexts, groupbys);
+ return {domains: domains, contexts: contexts, errors: errors, groupbys: groupbys};
},
/**
* Triggered after the SearchView has collected all relevant domains and
@@ -293,8 +366,7 @@ openerp.base.search.fields = new openerp.base.Registry({
'many2one': 'openerp.base.search.ManyToOneField',
'many2many': 'openerp.base.search.ManyToManyField'
});
-openerp.base.search.Invalid = Class.extend(
- /** @lends openerp.base.search.Invalid# */{
+openerp.base.search.Invalid = openerp.base.Class.extend( /** @lends openerp.base.search.Invalid# */{
/**
* Exception thrown by search widgets when they hold invalid values,
* which they can not return when asked.
@@ -314,14 +386,13 @@ openerp.base.search.Invalid = Class.extend(
': [' + this.value + '] is ' + this.message);
}
});
-openerp.base.search.Widget = openerp.base.Controller.extend(
- /** @lends openerp.base.search.Widget# */{
+openerp.base.search.Widget = openerp.base.Widget.extend( /** @lends openerp.base.search.Widget# */{
template: null,
/**
* Root class of all search widgets
*
* @constructs
- * @extends openerp.base.Controller
+ * @extends openerp.base.Widget
*
* @param view the ancestor view of this widget
*/
@@ -395,8 +466,6 @@ openerp.base.search.add_expand_listener = function($root) {
};
openerp.base.search.Group = openerp.base.search.Widget.extend({
template: 'SearchView.group',
- // TODO: contain stuff
- // TODO: @expand
init: function (view_section, view, fields) {
this._super(view);
this.attrs = view_section.attrs;
@@ -414,8 +483,7 @@ openerp.base.search.Group = openerp.base.search.Widget.extend({
}
});
-openerp.base.search.Input = openerp.base.search.Widget.extend(
- /** @lends openerp.base.search.Input# */{
+openerp.base.search.Input = openerp.base.search.Widget.extend( /** @lends openerp.base.search.Input# */{
/**
* @constructs
* @extends openerp.base.search.Widget
@@ -437,7 +505,6 @@ openerp.base.search.Input = openerp.base.search.Widget.extend(
});
openerp.base.search.Filter = openerp.base.search.Input.extend({
template: 'SearchView.filter',
- // TODO: force rendering
init: function (node, view) {
this._super(view);
this.attrs = node.attrs;
@@ -486,13 +553,9 @@ openerp.base.search.Filter = openerp.base.search.Input.extend({
return this.attrs.domain;
}
});
-openerp.base.search.Field = openerp.base.search.Input.extend(
- /** @lends openerp.base.search.Field# */ {
+openerp.base.search.Field = openerp.base.search.Input.extend( /** @lends openerp.base.search.Field# */ {
template: 'SearchView.field',
default_operator: '=',
- // TODO: set default values
- // TODO: get context, domain
- // TODO: holds Filters
/**
* @constructs
* @extends openerp.base.search.Input
@@ -530,9 +593,7 @@ openerp.base.search.Field = openerp.base.search.Input.extend(
},
get_domain: function () {
var val = this.get_value();
-
- var has_value = (val !== null && val !== '');
- if(!has_value) {
+ if (val === null || val === '') {
return;
}
@@ -544,9 +605,7 @@ openerp.base.search.Field = openerp.base.search.Input.extend(
this.get_value()
]];
}
- return _.extend(
- {}, domain,
- {own_values: {self: val}});
+ return _.extend({}, domain, {own_values: {self: val}});
}
});
/**
@@ -559,8 +618,7 @@ openerp.base.search.Field = openerp.base.search.Input.extend(
* @class
* @extends openerp.base.search.Field
*/
-openerp.base.search.CharField = openerp.base.search.Field.extend(
- /** @lends openerp.base.search.CharField# */ {
+openerp.base.search.CharField = openerp.base.search.Field.extend( /** @lends openerp.base.search.CharField# */ {
default_operator: 'ilike',
get_value: function () {
return this.$element.val();
@@ -599,32 +657,40 @@ openerp.base.search.BooleanField = openerp.base.search.Field.extend({
}
}
});
-openerp.base.search.IntegerField = openerp.base.search.Field.extend({
+openerp.base.search.NumberField = openerp.base.search.Field.extend(/** @lends openerp.base.search.NumberField# */{
get_value: function () {
if (!this.$element.val()) {
return null;
}
- var val = parseInt(this.$element.val());
- var check = Number(this.$element.val());
- if (isNaN(check) || val !== check) {
+ var val = this.parse(this.$element.val()),
+ check = Number(this.$element.val());
+ if (isNaN(val) || val !== check) {
this.$element.addClass('error');
throw new openerp.base.search.Invalid(
- this.attrs.name, this.$element.val(), "not a valid integer");
+ this.attrs.name, this.$element.val(), this.error_message);
}
this.$element.removeClass('error');
return val;
}
});
-openerp.base.search.FloatField = openerp.base.search.Field.extend({
- get_value: function () {
- var val = Number(this.$element.val());
- if (isNaN(val)) {
- this.$element.addClass('error');
- throw new openerp.base.search.Invalid(
- this.attrs.name, this.$element.val(), "not a valid number");
- }
- this.$element.removeClass('error');
- return val;
+/**
+ * @class
+ * @extends openerp.base.search.NumberField
+ */
+openerp.base.search.IntegerField = openerp.base.search.NumberField.extend(/** @lends openerp.base.search.IntegerField# */{
+ error_message: "not a valid integer",
+ parse: function (value) {
+ return parseInt(value, 10);
+ }
+});
+/**
+ * @class
+ * @extends openerp.base.search.NumberField
+ */
+openerp.base.search.FloatField = openerp.base.search.NumberField.extend(/** @lends openerp.base.search.FloatField# */{
+ error_message: "not a valid number",
+ parse: function (value) {
+ return parseFloat(value);
}
});
openerp.base.search.SelectionField = openerp.base.search.Field.extend({
@@ -633,12 +699,7 @@ openerp.base.search.SelectionField = openerp.base.search.Field.extend({
return this.$element.val();
}
});
-/**
- * @class
- * @extends openerp.base.search.Field
- */
-openerp.base.search.DateField = openerp.base.search.Field.extend(
- /** @lends openerp.base.search.DateField# */{
+openerp.base.search.DateField = openerp.base.search.Field.extend( /** @lends openerp.base.search.DateField# */{
template: 'SearchView.fields.date',
/**
* enables date picker on the HTML widgets
@@ -663,8 +724,12 @@ openerp.base.search.DateField = openerp.base.search.Field.extend(
get_values: function () {
var values_array = this.$element.find('input').serializeArray();
- var from = values_array[0].value;
- var to = values_array[1].value;
+ if (!values_array || !values_array[0]) {
+ throw new openerp.base.search.Invalid(
+ this.attrs.name, null, "widget not ready");
+ }
+ var from = values_array[0].value,
+ to = values_array[1].value;
var field_values = {};
if (from) {
@@ -709,33 +774,100 @@ openerp.base.search.DateField = openerp.base.search.Field.extend(
openerp.base.search.DateTimeField = openerp.base.search.DateField.extend({
// TODO: time?
});
-openerp.base.search.OneToManyField = openerp.base.search.IntegerField.extend({
+openerp.base.search.OneToManyField = openerp.base.search.CharField.extend({
// TODO: .relation, .context, .domain
});
-openerp.base.search.ManyToOneField = openerp.base.search.IntegerField.extend({
+openerp.base.search.ManyToOneField = openerp.base.search.CharField.extend({
// TODO: @widget
// TODO: .relation, .selection, .context, .domain
+ init: function (view_section, field, view) {
+ this._super(view_section, field, view);
+ var self = this;
+ this.got_name = $.Deferred().then(function () {
+ self.$element.val(self.name);
+ });
+ this.dataset = new openerp.base.DataSet(
+ this.view, this.attrs['relation']);
+ },
+ start: function () {
+ this._super();
+ this.setup_autocomplete();
+ var started = $.Deferred();
+ this.got_name.then(function () { started.resolve();},
+ function () { started.resolve(); });
+ return started.promise();
+ },
+ setup_autocomplete: function () {
+ var self = this;
+ this.$element.autocomplete({
+ source: function (req, resp) {
+ self.dataset.name_search(
+ req.term, self.attrs.domain, 'ilike', 8, function (data) {
+ resp(_.map(data, function (result) {
+ return {id: result[0], label: result[1]}
+ }));
+ });
+ },
+ select: function (event, ui) {
+ self.id = ui.item.id;
+ self.name = ui.item.label;
+ },
+ delay: 0
+ })
+ },
+ on_name_get: function (name_get) {
+ if (!name_get.length) {
+ delete this.id;
+ this.got_name.reject();
+ return;
+ }
+ this.name = name_get[0][1];
+ this.got_name.resolve();
+ },
+ render: function (defaults) {
+ if (defaults[this.attrs.name]) {
+ this.id = defaults[this.attrs.name];
+ // TODO: maybe this should not be completely removed
+ delete defaults[this.attrs.name];
+ this.dataset.name_get([this.id], $.proxy(this, 'on_name_get'));
+ } else {
+ this.got_name.reject();
+ }
+ return this._super(defaults);
+ },
+ get_domain: function () {
+ if (this.id && this.name) {
+ if (this.$element.val() === this.name) {
+ return [[this.attrs.name, '=', this.id]];
+ } else {
+ delete this.id;
+ delete this.name;
+ }
+ }
+ return this._super();
+ }
});
-openerp.base.search.ManyToManyField = openerp.base.search.IntegerField.extend({
+openerp.base.search.ManyToManyField = openerp.base.search.CharField.extend({
// TODO: .related_columns (Array), .context, .domain
});
-openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearch = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search',
identifier_prefix: 'extended-search',
- init: function (parent, session, model) {
- this._super(parent, session);
+ init: function (parent, model) {
+ this._super(parent);
this.model = model;
},
add_group: function() {
var group = new openerp.base.search.ExtendedSearchGroup(this, this.fields);
- var render = group.render();
- this.$element.find('.searchview_extended_groups_list').append(render);
- group.start();
+ group.appendTo(this.$element.find('.searchview_extended_groups_list'));
this.check_last_element();
},
start: function () {
this._super();
+ if (!this.$element) {
+ return; // not a logical state but sometimes it happens
+ }
this.$element.closest("table.oe-searchview-render-line").css("display", "none");
var self = this;
this.rpc("/base/searchview/fields_get",
@@ -751,10 +883,13 @@ openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
return null;
},
get_domain: function() {
+ if (!this.$element) {
+ return null; // not a logical state but sometimes it happens
+ }
if(this.$element.closest("table.oe-searchview-render-line").css("display") == "none") {
return null;
}
- return _.reduce(this.children,
+ return _.reduce(this.widget_children,
function(mem, x) { return mem.concat(x.get_domain());}, []);
},
on_activate: function() {
@@ -773,12 +908,14 @@ openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
}
},
check_last_element: function() {
- _.each(this.children, function(x) {x.set_last_group(false);});
- this.children[this.children.length - 1].set_last_group(true);
+ _.each(this.widget_children, function(x) {x.set_last_group(false);});
+ if (this.widget_children.length >= 1) {
+ this.widget_children[this.widget_children.length - 1].set_last_group(true);
+ }
}
});
-openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearchGroup = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.group',
identifier_prefix: 'extended-search-group',
init: function (parent, fields) {
@@ -787,7 +924,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
},
add_prop: function() {
var prop = new openerp.base.search.ExtendedSearchProposition(this, this.fields);
- var render = prop.render({'index': this.children.length - 1});
+ var render = prop.render({'index': this.widget_children.length - 1});
this.$element.find('.searchview_extended_propositions_list').append(render);
prop.start();
},
@@ -804,7 +941,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
});
},
get_domain: function() {
- var props = _(this.children).chain().map(function(x) {
+ var props = _(this.widget_children).chain().map(function(x) {
return x.get_proposition();
}).compact().value();
var choice = this.$element.find(".searchview_extended_group_choice").val();
@@ -814,9 +951,9 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
props);
},
stop: function() {
- var parent = this.parent;
- if (this.parent.children.length == 1)
- this.parent.hide();
+ var parent = this.widget_parent;
+ if (this.widget_parent.widget_children.length == 1)
+ this.widget_parent.hide();
this._super();
parent.check_last_element();
},
@@ -828,7 +965,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
}
});
-openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearchProposition = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition',
identifier_prefix: 'extended-search-proposition',
init: function (parent, fields) {
@@ -853,9 +990,12 @@ openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({
});
},
stop: function() {
- if (this.parent.children.length == 1)
- this.parent.stop();
+ var parent;
+ if (this.widget_parent.widget_children.length == 1)
+ parent = this.widget_parent;
this._super();
+ if (parent)
+ parent.stop();
},
changed: function() {
var nval = this.$element.find(".searchview_extended_prop_field").val();
@@ -887,7 +1027,7 @@ openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({
if (! e instanceof openerp.base.KeyNotFound) {
throw e;
}
- var type = "char";
+ type = "char";
this.log('Unknow field type ' + e.key);
}
this.value = new (openerp.base.search.custom_filters.get_object(type))
@@ -915,7 +1055,7 @@ openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({
}
});
-openerp.base.search.ExtendedSearchProposition.Char = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearchProposition.Char = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.char',
identifier_prefix: 'extended-search-proposition-char',
operators: [
@@ -932,7 +1072,7 @@ openerp.base.search.ExtendedSearchProposition.Char = openerp.base.BaseWidget.ext
return this.$element.val();
}
});
-openerp.base.search.ExtendedSearchProposition.DateTime = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearchProposition.DateTime = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.datetime',
identifier_prefix: 'extended-search-proposition-datetime',
operators: [
@@ -954,7 +1094,7 @@ openerp.base.search.ExtendedSearchProposition.DateTime = openerp.base.BaseWidget
});
}
});
-openerp.base.search.ExtendedSearchProposition.Date = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearchProposition.Date = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.date',
identifier_prefix: 'extended-search-proposition-date',
operators: [
@@ -976,7 +1116,7 @@ openerp.base.search.ExtendedSearchProposition.Date = openerp.base.BaseWidget.ext
});
}
});
-openerp.base.search.ExtendedSearchProposition.Integer = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearchProposition.Integer = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.integer',
identifier_prefix: 'extended-search-proposition-integer',
operators: [
@@ -988,15 +1128,14 @@ openerp.base.search.ExtendedSearchProposition.Integer = openerp.base.BaseWidget.
{value: "<=", text: "less or equal than"}
],
get_value: function() {
- val = this.$element.val();
- val2 = parseFloat(val);
- if(val2 != 0 && !val2) {
+ var value = parseFloat(this.$element.val());
+ if(value != 0 && !value) {
return "";
}
- return Math.round(val2);
+ return Math.round(value);
}
});
-openerp.base.search.ExtendedSearchProposition.Float = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearchProposition.Float = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.float',
identifier_prefix: 'extended-search-proposition-float',
operators: [
@@ -1008,15 +1147,14 @@ openerp.base.search.ExtendedSearchProposition.Float = openerp.base.BaseWidget.ex
{value: "<=", text: "less or equal than"}
],
get_value: function() {
- val = this.$element.val();
- val2 = parseFloat(val);
- if(val2 != 0 && !val2) {
+ var value = parseFloat(this.$element.val());
+ if(value != 0 && !value) {
return "";
}
- return val2;
+ return value;
}
});
-openerp.base.search.ExtendedSearchProposition.Selection = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearchProposition.Selection = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.selection',
identifier_prefix: 'extended-search-proposition-selection',
operators: [
@@ -1030,7 +1168,7 @@ openerp.base.search.ExtendedSearchProposition.Selection = openerp.base.BaseWidge
return this.$element.val();
}
});
-openerp.base.search.ExtendedSearchProposition.Boolean = openerp.base.BaseWidget.extend({
+openerp.base.search.ExtendedSearchProposition.Boolean = openerp.base.OldWidget.extend({
template: 'SearchView.extended_search.proposition.boolean',
identifier_prefix: 'extended-search-proposition-boolean',
operators: [
diff --git a/addons/base/static/src/js/view_help.js b/addons/base/static/src/js/view_help.js
new file mode 100644
index 00000000000..aa98f59c3ab
--- /dev/null
+++ b/addons/base/static/src/js/view_help.js
@@ -0,0 +1,15 @@
+/*---------------------------------------------------------
+ * OpenERP base library
+ *---------------------------------------------------------*/
+
+openerp.base.view_help = function(openerp) {
+
+openerp.base.ProcessView = openerp.base.Widget.extend({
+});
+
+openerp.base.HelpView = openerp.base.Widget.extend({
+});
+
+};
+
+// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/base/static/src/js/tree.js b/addons/base/static/src/js/view_tree.js
index e333c295473..90972f84973 100644
--- a/addons/base/static/src/js/tree.js
+++ b/addons/base/static/src/js/view_tree.js
@@ -2,10 +2,10 @@
* OpenERP base library
*---------------------------------------------------------*/
-openerp.base.tree = function(openerp) {
+openerp.base.view_tree = function(openerp) {
openerp.base.views.add('tree', 'openerp.base.TreeView');
-openerp.base.TreeView = openerp.base.Controller.extend({
+openerp.base.TreeView = openerp.base.Widget.extend({
/**
* Genuine tree view (the one displayed as a tree, not the list)
*/
diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js
index ee4388c8971..7e95ff67f83 100644
--- a/addons/base/static/src/js/views.js
+++ b/addons/base/static/src/js/views.js
@@ -4,84 +4,117 @@
openerp.base.views = function(openerp) {
-openerp.base.ActionManager = openerp.base.Controller.extend({
+openerp.base.ActionManager = openerp.base.Widget.extend({
// process all kind of actions
- init: function(session, element_id) {
- this._super(session, element_id);
+ init: function(parent, element_id) {
+ this._super(parent, element_id);
this.viewmanager = null;
- this.dialog_stack = [];
+ this.current_dialog = null;
// Temporary linking view_manager to session.
- // Will use controller_parent to find it when implementation will be done.
- session.action_manager = this;
+ // Will use parent to find it when implementation will be done.
+ this.session.action_manager = this;
},
/**
* Process an action
* Supported actions: act_window
*/
+ action_window: function() {
+ },
+ action_window_close: function() {
+ },
+ action_server: function() {
+ },
+ action_url: function() {
+ },
+ action_report: function() {
+ },
+ action_client: function() {
+ },
do_action: function(action, on_closed) {
var self = this;
action.flags = _.extend({
sidebar : action.target != 'new',
- search_view : true,
+ search_view : action.target != 'new',
new_window : false,
- views_switcher : true,
- action_buttons : true,
- pager : true
+ views_switcher : action.target != 'new',
+ action_buttons : action.target != 'new',
+ pager : action.target != 'new'
}, action.flags || {});
// instantiate the right controllers by understanding the action
switch (action.type) {
case 'ir.actions.act_window':
- if (!action.target && this.dialog_stack.length) {
+ if (!action.target && this.current_dialog) {
action.flags.new_window = true;
}
if (action.target == 'new') {
- var element_id = _.uniqueId("act_window_dialog");
- $('<div>', {id: element_id}).dialog({
- title: action.name,
- modal: true,
- width: '50%',
- height: 'auto'
- }).bind('dialogclose', function(event) {
- // When dialog is closed with ESC key or close manually, branch to act_window_close logic
- self.do_action({ type: 'ir.actions.act_window_close' });
- });
- var viewmanager = new openerp.base.ViewManagerAction(this.session, element_id, action);
+ var dialog = this.current_dialog = new openerp.base.ActionDialog(this, { title: action.name, width: '90%' });
+ if (on_closed) {
+ dialog.close_callback = on_closed;
+ }
+ dialog.start(false);
+ var viewmanager = dialog.viewmanager = new openerp.base.ViewManagerAction(this, dialog.element_id, action);
viewmanager.start();
- viewmanager.on_act_window_closed.add(on_closed);
- viewmanager.is_dialog = true;
- this.dialog_stack.push(viewmanager);
+ dialog.open();
} else if (action.flags.new_window) {
action.flags.new_window = false;
this.rpc("/base/session/save_session_action", { the_action : action}, function(key) {
var url = window.location.protocol + "//" + window.location.host +
window.location.pathname + "?" + jQuery.param({ s_action : "" + key });
window.open(url);
+ if (on_closed) {
+ on_closed();
+ }
});
} else {
if (this.viewmanager) {
this.viewmanager.stop();
}
- this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action);
+ this.viewmanager = new openerp.base.ViewManagerAction(this, this.element_id, action);
this.viewmanager.start();
}
break;
case 'ir.actions.act_window_close':
- var dialog = this.dialog_stack.pop();
- if (!action.special) {
- dialog.on_act_window_closed();
- }
- dialog.$element.dialog('destroy');
- dialog.stop();
+ this.close_dialog();
+ break;
+ case 'ir.actions.server':
+ this.rpc('/base/action/run', {
+ action_id: action.id,
+ context: {active_id: 66, active_ids: [66], active_model: 'ir.ui.menu'}
+ }).then(function (action) {
+ self.do_action(action, on_closed)
+ });
break;
default:
console.log("Action manager can't handle action of type " + action.type, action);
}
+ },
+ close_dialog: function() {
+ if (this.current_dialog) {
+ this.current_dialog.stop();
+ this.current_dialog = null;
+ }
+ }
+});
+
+openerp.base.ActionDialog = openerp.base.Dialog.extend({
+ identifier_prefix: 'action_dialog',
+ on_close: function() {
+ this._super(this, arguments);
+ if (this.close_callback) {
+ this.close_callback();
+ }
+ },
+ stop: function() {
+ this._super(this, arguments);
+ if (this.viewmanager) {
+ this.viewmanager.stop();
+ }
}
});
-openerp.base.ViewManager = openerp.base.Controller.extend({
- init: function(session, element_id, dataset, views) {
- this._super(session, element_id);
+openerp.base.ViewManager = openerp.base.Widget.extend({
+ init: function(parent, element_id, dataset, views) {
+ this._super(parent, element_id);
this.model = dataset.model;
this.dataset = dataset;
this.searchview = null;
@@ -90,14 +123,13 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
{return x instanceof Array? {view_id: x[0], view_type: x[1]} : x;});
this.views = {};
this.flags = this.flags || {};
- this.sidebar = new openerp.base.NullSidebar();
this.registry = openerp.base.views;
- this.is_dialog = false;
},
/**
* @returns {jQuery.Deferred} initial view loading promise
*/
start: function() {
+ this._super();
var self = this;
this.dataset.start();
this.$element.html(QWeb.render("ViewManager", {"prefix": this.element_id, views: this.views_src}));
@@ -105,7 +137,12 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
self.on_mode_switch($(this).data('view-type'));
});
_.each(this.views_src, function(view) {
- self.views[view.view_type] = $.extend({}, view, {controller: null});
+ self.views[view.view_type] = $.extend({}, view, {
+ controller : null,
+ options : _.extend({
+ sidebar_id : self.element_id + '_sidebar_' + view.view_type
+ }, self.flags)
+ });
});
if (this.flags.views_switcher === false) {
this.$element.find('.oe_vm_switch').hide();
@@ -129,7 +166,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
if (!view.controller) {
// Lazy loading of views
var controllerclass = this.registry.get_object(view_type);
- var controller = new controllerclass( this, this.session, this.element_id + "_view_" + view_type,
+ var controller = new controllerclass(this, this.element_id + '_view_' + view_type,
this.dataset, view.view_id, view.options);
if (view.embedded_view) {
controller.set_embedded_view(view.embedded_view);
@@ -183,7 +220,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
},
/**
* Event launched when a controller has been inited.
- *
+ *
* @param {String} view_type type of view
* @param {String} view the inited controller
*/
@@ -199,7 +236,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
if (this.searchview) {
this.searchview.stop();
}
- this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search", this.dataset, view_id, search_defaults);
+ this.searchview = new openerp.base.SearchView(this, this.element_id + "_search", this.dataset, view_id, search_defaults);
if (this.flags.search_view === false) {
this.searchview.hide();
}
@@ -211,11 +248,6 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
return this.searchview.start();
},
/**
- * Called when this view manager has been created by an action 'act_window@target=new' is closed
- */
- on_act_window_closed : function() {
- },
- /**
* Called when one of the view want to execute an action
*/
on_action: function(action) {
@@ -246,45 +278,39 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
});
openerp.base.NullViewManager = openerp.base.generate_null_object_class(openerp.base.ViewManager, {
- init: function() {
- this._super();
+ init: function(parent) {
+ this._super(parent);
+ if(parent)
+ this.session = parent.session;
this.action = {flags: {}};
- this.sidebar = new openerp.base.NullSidebar();
}
});
+// TODO Will move to action Manager
openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
- init: function(session, element_id, action) {
+ init: function(parent, element_id, action) {
+ this.session = parent.session;
var dataset;
if (!action.res_id) {
- dataset = new openerp.base.DataSetSearch(session, action.res_model, action.context || null);
+ dataset = new openerp.base.DataSetSearch(this, action.res_model, action.context || null);
} else {
- dataset = new openerp.base.DataSetStatic(session, action.res_model, {}, [action.res_id]);
+ dataset = new openerp.base.DataSetStatic(this, action.res_model, {}, [action.res_id]);
if (action.context) {
// TODO fme: should normalize all DataSets constructors to (session, model, context, ...)
dataset.context = action.context;
}
}
- this._super(session, element_id, dataset, action.views);
+ this._super(parent, element_id, dataset, action.views);
this.action = action;
this.flags = this.action.flags || {};
if (action.res_model == 'board.board' && action.views.length == 1 && action.views) {
// Not elegant but allows to avoid flickering of SearchView#do_hide
this.flags.search_view = this.flags.pager = this.flags.sidebar = this.flags.action_buttons = false;
}
- if (this.flags.sidebar) {
- this.sidebar = new openerp.base.Sidebar(null, this);
- }
},
start: function() {
var inital_view_loaded = this._super();
- // init sidebar
- if (this.flags.sidebar) {
- this.$element.find('.view-manager-main-sidebar').html(this.sidebar.render());
- this.sidebar.start();
- }
-
var search_defaults = {};
_.each(this.action.context, function (value, key) {
var match = /^search_default_(.*)$/.exec(key);
@@ -308,8 +334,7 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
}
},
stop: function() {
- // should be replaced by automatic destruction implemented in BaseWidget
- this.sidebar.stop();
+ // should be replaced by automatic destruction implemented in Widget
this._super();
},
/**
@@ -336,82 +361,101 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
}
});
-openerp.base.Sidebar = openerp.base.BaseWidget.extend({
- template: "ViewManager.sidebar",
- init: function(parent, view_manager) {
- this._super(parent, view_manager.session);
- this.view_manager = view_manager;
- this.sections = [];
- },
- set_toolbar: function(toolbar) {
- this.sections = [];
- var self = this;
- _.each([["print", "Reports"], ["action", "Actions"], ["relate", "Links"]], function(type) {
- if (toolbar[type[0]].length == 0)
- return;
- var section = {elements:toolbar[type[0]], label:type[1]};
- self.sections.push(section);
- });
- this.do_refresh(true);
+openerp.base.Sidebar = openerp.base.Widget.extend({
+ init: function(parent, element_id) {
+ this._super(parent, element_id);
+ this.items = {};
+ this.sections = {};
},
- do_refresh: function(new_view) {
- var view = this.view_manager.active_view;
- var the_condition = this.sections.length > 0 && _.detect(this.sections,
- function(x) {return x.elements.length > 0;}) != undefined
- && (!new_view || view != 'list');
-
- this.$element.toggleClass('open-sidebar', the_condition)
- .toggleClass('closed-sidebar', !the_condition);
-
- this.$element.html(QWeb.render("ViewManager.sidebar.internal", { sidebar: this, view: view }));
-
+ start: function() {
var self = this;
+ this._super(this, arguments);
+ this.$element.html(QWeb.render('Sidebar'));
this.$element.find(".toggle-sidebar").click(function(e) {
- self.$element.toggleClass('open-sidebar closed-sidebar');
- e.stopPropagation();
- e.preventDefault();
+ self.do_toggle();
});
-
- this.$element.find("a.oe_sidebar_action_a").click(function(e) {
- var $this = jQuery(this);
- var index = $this.attr("data-index").split('-');
- var action = self.sections[index[0]].elements[index[1]];
- action.flags = {
- new_window : true
- };
- self.session.action_manager.do_action(action);
- e.stopPropagation();
- e.preventDefault();
+ },
+ add_toolbar: function(toolbar) {
+ var self = this;
+ _.each([['print', "Reports"], ['action', "Actions"], ['relate', "Links"]], function(type) {
+ var items = toolbar[type[0]];
+ if (items.length) {
+ for (var i = 0; i < items.length; i++) {
+ items[i] = {
+ label: items[i]['name'],
+ action: items[i],
+ classname: 'oe_sidebar_' + type[0]
+ }
+ }
+ self.add_section(type[0], type[1], items);
+ }
});
},
- start: function() {
- this._super();
- this.do_refresh(false);
- }
-});
-
-openerp.base.NullSidebar = openerp.base.generate_null_object_class(openerp.base.Sidebar);
-
-openerp.base.Export = openerp.base.Dialog.extend({
- dialog_title: "Export",
- template: 'ExportDialog',
- identifier_prefix: 'export_dialog',
- init: function (session, model, domain) {
- this._super();
+ add_section: function(code, name, items) {
+ // For each section, we pass a name/label and optionally an array of items.
+ // If no items are passed, then the section will be created as a custom section
+ // returning back an element_id to be used by a custom controller.
+ // Else, the section is a standard section with items displayed as links.
+ // An item is a dictonary : {
+ // label: label to be displayed for the link,
+ // action: action to be launch when the link is clicked,
+ // callback: a function to be executed when the link is clicked,
+ // classname: optional dom class name for the line,
+ // title: optional title for the link
+ // }
+ // Note: The item should have one action or/and a callback
+ var self = this,
+ section_id = _.uniqueId(this.element_id + '_section_' + code + '_');
+ if (items) {
+ for (var i = 0; i < items.length; i++) {
+ items[i].element_id = _.uniqueId(section_id + '_item_');
+ this.items[items[i].element_id] = items[i];
+ }
+ }
+ var $section = $(QWeb.render("Sidebar.section", {
+ section_id: section_id,
+ name: name,
+ classname: 'oe_sidebar_' + code,
+ items: items
+ }));
+ if (items) {
+ $section.find('a.oe_sidebar_action_a').click(function() {
+ var item = self.items[$(this).attr('id')];
+ if (item.callback) {
+ item.callback();
+ }
+ if (item.action) {
+ item.action.flags = item.action.flags || {};
+ item.action.flags.new_window = true;
+ self.do_action(item.action);
+ }
+ return false;
+ });
+ }
+ $section.appendTo(this.$element.find('div.sidebar-actions'));
+ this.sections[code] = $section;
+ return section_id;
},
- start: function () {
- this._super();
- this.$element.html(this.render());
+ do_fold: function() {
+ this.$element.addClass('closed-sidebar').removeClass('open-sidebar');
},
- on_button_Export: function() {
- console.log("Export")
+ do_unfold: function() {
+ this.$element.addClass('open-sidebar').removeClass('closed-sidebar');
},
- on_button_Cancel: function() {
- this.$element.dialog("close");
+ do_toggle: function() {
+ this.$element.toggleClass('open-sidebar closed-sidebar');
}
});
-openerp.base.View = openerp.base.Controller.extend({
+openerp.base.View = openerp.base.Widget.extend({
+ set_default_options: function(options) {
+ this.options = options || {};
+ _.defaults(this.options, {
+ // All possible views options should be defaulted here
+ sidebar_id: null,
+ sidebar: true
+ });
+ },
/**
* Fetches and executes the action identified by ``action_data``.
*
@@ -423,11 +467,15 @@ openerp.base.View = openerp.base.Controller.extend({
* @param {openerp.base.DataSet} dataset a dataset object used to communicate with the server
* @param {openerp.base.ActionManager} action_manager object able to actually execute the action, if any is fetched
* @param {Object} [record_id] the identifier of the object on which the action is to be applied
- * @param {Function} on_no_action callback to execute if the action does not generate any result (no new action)
+ * @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action)
*/
- execute_action: function (action_data, dataset, action_manager, record_id, on_no_action, on_closed) {
+ execute_action: function (action_data, dataset, action_manager, record_id, on_closed) {
var self = this;
+ if (action_manager.current_dialog) {
+ on_closed = action_manager.current_dialog.close_callback;
+ }
var handler = function (r) {
+ action_manager.close_dialog();
var action = r.result;
if (action && action.constructor == Object) {
action.context = action.context || {};
@@ -437,29 +485,16 @@ openerp.base.View = openerp.base.Controller.extend({
active_model: dataset.model
});
action.flags = {
- sidebar : false,
- search_view : false,
- views_switcher : false,
- action_buttons : false,
- pager : false
+ new_window: true
};
action_manager.do_action(action, on_closed);
- if (self.view_manager.is_dialog && action.type != 'ir.actions.act_window_close') {
- handler({
- result : { type: 'ir.actions.act_window_close' }
- });
- }
- } else {
- on_no_action(action);
+ } else if (on_closed) {
+ on_closed(action);
}
};
- if (action_data.special) {
- handler({
- result : { type: 'ir.actions.act_window_close', special: action_data.special }
- });
- } else {
- var context = new openerp.base.CompoundContext(dataset.context, action_data.context || {});
+ if (!action_data.special) {
+ var context = new openerp.base.CompoundContext(dataset.get_context(), action_data.context || {});
switch(action_data.type) {
case 'object':
return dataset.call_button(action_data.name, [[record_id], context], handler);
@@ -468,17 +503,73 @@ openerp.base.View = openerp.base.Controller.extend({
default:
return dataset.exec_workflow(record_id, action_data.name, handler);
}
+ } else {
+ action_manager.close_dialog();
}
},
/**
* Directly set a view to use instead of calling fields_view_get. This method must
* be called before start(). When an embedded view is set, underlying implementations
* of openerp.base.View must use the provided view instead of any other one.
- *
+ *
* @param embedded_view A view.
*/
set_embedded_view: function(embedded_view) {
this.embedded_view = embedded_view;
+ },
+ set_common_sidebar_sections: function(sidebar) {
+ sidebar.add_section('customize', "Customize", [
+ {
+ label: "Manage Views",
+ callback: this.on_sidebar_manage_view,
+ title: "Manage views of the current object"
+ }, {
+ label: "Edit Workflow",
+ callback: this.on_sidebar_edit_workflow,
+ title: "Manage views of the current object",
+ classname: 'oe_hide oe_sidebar_edit_workflow'
+ }, {
+ label: "Customize Object",
+ callback: this.on_sidebar_customize_object,
+ title: "Manage views of the current object"
+ }
+ ]);
+ sidebar.add_section('other', "Other Options", [
+ {
+ label: "Import",
+ callback: this.on_sidebar_import
+ }, {
+ label: "Export",
+ callback: this.on_sidebar_export
+ }, {
+ label: "Translate",
+ callback: this.on_sidebar_translate,
+ classname: 'oe_hide oe_sidebar_translate'
+ }, {
+ label: "View Log",
+ callback: this.on_sidebar_view_log,
+ classname: 'oe_hide oe_sidebar_view_log'
+ }
+ ]);
+ },
+ on_sidebar_manage_view: function() {
+ console.log('Todo');
+ },
+ on_sidebar_edit_workflow: function() {
+ console.log('Todo');
+ },
+ on_sidebar_customize_object: function() {
+ console.log('Todo');
+ },
+ on_sidebar_import: function() {
+ },
+ on_sidebar_export: function() {
+ var export_view = new openerp.base.DataExport(this, this.dataset);
+ export_view.start(false);
+ },
+ on_sidebar_translate: function() {
+ },
+ on_sidebar_view_log: function() {
}
});
@@ -487,12 +578,6 @@ openerp.base.View = openerp.base.Controller.extend({
*/
openerp.base.views = new openerp.base.Registry();
-openerp.base.ProcessView = openerp.base.Controller.extend({
-});
-
-openerp.base.HelpView = openerp.base.Controller.extend({
-});
-
openerp.base.json_node_to_xml = function(node, single_quote, indent) {
// For debugging purpose, this function will convert a json node back to xml
// Maybe usefull for xml view editor
diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml
index 9bef00147d3..c51284ad63a 100644
--- a/addons/base/static/src/xml/base.xml
+++ b/addons/base/static/src/xml/base.xml
@@ -62,7 +62,7 @@
<td><label for="db">Database:</label></td>
<td>
<t t-if="!db_list">
- <input type="text" name="db" value="trunk" autofocus="true"/>
+ <input type="text" name="db" t-att-value="selected_db || ''" autofocus="true"/>
</t>
<t t-if="db_list">
<select name="db">
@@ -87,18 +87,7 @@
<tr>
<td><label for="password">Password:</label></td>
<td><input type="password" name="password"
- t-att-value="selected_password || ''"/></td>
- </tr>
- <tr t-if="has_local_storage">
- <td><label for="remember">Remember password:</label></td>
- <td class="oe_remember">
- <t t-if="remember">
- <input type="checkbox" name="remember" checked="yes"/>
- </t>
- <t t-if="!remember">
- <input type="checkbox" name="remember"/>
- </t>
- </td>
+ t-att-value="selected_password || ''"/></td>
</tr>
<tr>
<td></td>
@@ -114,7 +103,7 @@
<div class="oe_login_right_pane">
<p>We think that daily job activities can be more intuitive, efficient, automated, .. and even fun.</p>
<h3>OpenERP's vision to be:</h3>
-
+
<table cellpadding="0" cellspacing="0" width="100%" style="border:none;">
<tbody>
<tr>
@@ -146,7 +135,7 @@
</tr>
</tbody>
</table>
-
+
</div>
</t>
<t t-name="Header">
@@ -154,8 +143,8 @@
<div class="company_logo" />
</a>
<h1 class="header_title" t-if="session.session_is_valid()">
- <span class="company">$company</span> - (<span class="database">$database</span>)<br/>
- <small class="username">$username session_id: <t t-esc="session.session_id"/></small>
+ <span class="database"><t t-esc="session.db"/></span> - <t t-esc="session.login"/> <br/>
+ <small class="username">rpc_session_id: <t t-esc="session.session_id"/></small>
</h1>
<div class="header_corner">
<ul class="block" t-if="session.session_is_valid()">
@@ -243,42 +232,66 @@
</t>
</td>
<td class="view-manager-main-sidebar" height="100%">
+ <t t-foreach="views" t-as="view">
+ <div t-attf-id="#{prefix}_sidebar_#{view.view_type}" class="sidebar-main-div closed-sidebar" style="display: none"/>
+ </t>
</td>
</tr>
</table>
</t>
-<table t-name="ListView">
+<t t-name="Sidebar">
+ <a class="toggle-sidebar"></a>
+ <div class="sidebar-content">
+ <div class="sidebar-actions">
+ </div>
+ </div>
+</t>
+<t t-name="Sidebar.section">
+ <h2><t t-esc="name"/></h2>
+ <div t-att-id="section_id" t-att-class="classname">
+ <ul t-if="items">
+ <li t-foreach="items" t-as="item" t-att-class="item.classname">
+ <a class="oe_sidebar_action_a" t-att-id="item.element_id" t-att-title="item.title" href="#">
+ <t t-esc="item.label"/>
+ </a>
+ </li>
+ </ul>
+ </div>
+</t>
+<table t-name="ListView" class="oe-listview-content">
<t t-set="columns_count" t-value="visible_columns.length + (options.selectable ? 1 : 0) + (options.deletable ? 1 : 0)"/>
- <t t-set="actions_span" t-value="Math.floor((options.deletable or options.addable) ? columns_count/2 : 0)"/>
<thead class="ui-widget-header">
- <tr>
- <th t-if="actions_span" t-att-colspan="actions_span"
- class="oe-actions">
- <t t-if="flags.action_buttons !== false">
- <button type="button" id="oe-list-add"
- t-if="options.addable">
- <t t-esc="options.addable"/>
- </button>
- <button type="button" id="oe-list-delete"
- t-if="options.selectable and options.deletable">
- Delete
- </button>
- </t>
- </th>
- <th t-att-colspan="columns_count - actions_span"
- class="oe-list-pager">
- <t t-if="flags.pager !== false">
- <button type="button" data-pager-action="first">First</button>
- <button type="button" data-pager-action="previous"
- >&lt;&lt;</button>
+ <tr t-if="options.action_buttons !== false or options.pager !== false">
+ <th t-att-colspan="columns_count">
+ <table>
+ <tr>
+ <td t-if="options.action_buttons !== false" class="oe-actions">
+ <button type="button" class="oe-list-add"
+ t-if="options.addable">
+ <t t-esc="options.addable"/>
+ </button>
+ <button type="button" class="oe-list-delete"
+ t-if="options.selectable and options.deletable">
+ Delete
+ </button>
+ </td>
+ <th t-if="options.pager !== false" class="oe-list-pager">
+ <button type="button" disabled="disabled"
+ data-pager-action="first">First</button>
+ <button type="button" disabled="disabled"
+ data-pager-action="previous"
+ >&lt;</button>
- <span class="oe-pager-first">1</span>
- to <span class="oe-pager-last">1</span>
- of <span class="oe-pager-total">1</span>
+ <span class="oe-pager-state">
+ </span>
- <button type="button" data-pager-action="next">&gt;&gt;</button>
- <button type="button" data-pager-action="last">Last</button>
- </t>
+ <button type="button" disabled="disabled"
+ data-pager-action="next">&gt;</button>
+ <button type="button" disabled="disabled"
+ data-pager-action="last">Last</button>
+ </th>
+ </tr>
+ </table>
</th>
</tr>
<tr t-if="options.header">
@@ -287,24 +300,22 @@
<t t-esc="column.string"/>
</th>
</t>
- <th t-if="options.selectable"/>
+ <th t-if="options.selectable" width="1"/>
<t t-foreach="columns" t-as="column">
<th t-if="!column.meta and column.invisible !== '1'" t-att-data-id="column.id"
t-att-class="((options.sortable and column.tag !== 'button') ? 'oe-sortable' : null)">
- <t t-if="column.tag !== 'button'">
- <t t-esc="column.string"/>
- <span t-att-class="(fields_view.sorted.field === column.id) ? ('ui-icon' + (fields_view.sorted.reversed ? ' ui-icon-triangle-1-n' : ' ui-icon-triangle-1-s')) : ''"/>
- </t>
+ <t t-if="column.tag !== 'button'"
+ ><t t-esc="column.string"/></t>
</th>
</t>
- <th t-if="options.deletable"/>
+ <th t-if="options.deletable" width="1"/>
</tr>
</thead>
<tfoot class="ui-widget-header">
<tr>
<td t-if="options.selectable"/>
<td t-foreach="aggregate_columns" t-as="column" class="oe-list-footer oe-number"
- t-att-data-field="column.field" t-att-title="column.label">
+ t-att-data-field="column.id" t-att-title="column.label">
</td>
<td t-if="options.deletable"/>
</tr>
@@ -322,10 +333,10 @@
t-att-data-index="row_index">
<t t-foreach="columns" t-as="column">
<td t-if="column.meta">
-
+
</td>
</t>
- <th t-if="options.selectable" class="oe-record-selector">
+ <th t-if="options.selectable" class="oe-record-selector" width="1">
<input type="checkbox"/>
</th>
<t t-foreach="columns" t-as="column">
@@ -333,23 +344,10 @@
<td t-if="!column.meta and column.invisible !== '1'" t-att-title="column.help"
t-att-class="'oe-field-cell' + (align ? ' oe-number' : '')"
t-att-data-field="column.id">
- <t t-set="attrs" t-value="column.attrs_for(row.data)"/>
- <t t-if="!attrs.invisible">
- <t t-set="is_button" t-value="column.tag === 'button'"/>
- <!-- TODO: get correct widget from form -->
- <t t-if="!is_button and row['data'][column.id].value !== false">
- <t t-set="value" t-value="row['data'][column.id].value"/>
- <t t-esc="value instanceof Array ? value[1] : value"/>
- </t>
- <button type="button" t-att-title="column.string"
- t-if="is_button">
- <img t-att-src="'/base/static/src/img/icons/' + column.icon + '.png'"
- t-att-alt="column.string"/>
- </button>
- </t>
+ <t t-raw="render_cell(row.data, column)"/>
</td>
</t>
- <td t-if="options.deletable" class='oe-record-delete'>
+ <td t-if="options.deletable" class='oe-record-delete' width="1">
<button type="button" name="delete">♻</button>
</td>
</tr>
@@ -357,9 +355,9 @@
<t t-raw="frame.render()"/>
</t>
<t t-name="FormView">
- <h2 class="oe_view_title"><t t-esc="view.fields_view.arch.attrs.string"/></h2>
<div class="oe_form_header" t-att-id="view.element_id + '_header'">
- <div class="oe_form_buttons" t-if="view.flags.action_buttons !== false">
+ <h2 class="oe_view_title"><t t-esc="view.fields_view.arch.attrs.string"/> <button class="oe_get_xml_view">xml</button></h2>
+ <div class="oe_form_buttons" t-if="view.options.action_buttons !== false">
<!--<button type="button" class="oe_form_button_save">
<span class="oe_form_on_update">Save</span>
<span class="oe_form_on_create">Create</span>
@@ -371,7 +369,7 @@
<!--<button type="button" class="oe_form_button_cancel">Cancel</button>-->
<button type="button" class="oe_form_button_new">New</button>
</div>
- <div class="oe_form_pager" t-if="view.flags.pager !== false">
+ <div class="oe_form_pager" t-if="view.options.pager !== false">
<button type="button" data-pager-action="first">First</button>
<button type="button" data-pager-action="previous">&lt;&lt;</button>
@@ -384,21 +382,20 @@
<t t-raw="frame.render()"/>
</t>
<t t-name="FormView.sidebar.attachments">
- <h2>Attachments</h2>
<div class="oe-sidebar-attachments-toolbar">
<div class="oe-binary-file-set" style="float: right">
<form class="oe-binary-form" t-attf-target="#{element_id}_iframe"
method="post" enctype="multipart/form-data" action="/base/binary/upload_attachment">
<input type="hidden" name="session_id" t-att-value="session.session_id"/>
<input type="hidden" name="callback" t-attf-value="#{element_id}_iframe"/>
- <input type="hidden" name="model" t-att-value="dataset.model"/>
- <input type="hidden" name="id" t-att-value="datarecord.id"/>
+ <input type="hidden" name="model" t-att-value="view.dataset.model"/>
+ <input type="hidden" name="id" t-att-value="view.datarecord.id"/>
<button class="button" type="button">
<img src="/base/static/src/img/throbber.gif" width="16" height="16" style="display: none"/>
<span>Add</span>
</button>
<input type="file" class="oe-binary-file" name="ufile" title="Add attachment"
- t-att-onclick="datarecord.id ? null : 'alert(\'No record selected ! You can only attach to existing record.\'); return false;'"/>
+ t-att-onclick="view.datarecord.id ? null : 'alert(\'No record selected ! You can only attach to existing record.\'); return false;'"/>
</form>
<iframe t-attf-id="#{element_id}_iframe" t-attf-name="#{element_id}_iframe" style="display: none"> </iframe>
</div>
@@ -408,7 +405,7 @@
<li t-foreach="attachments" t-as="attachment">
<t t-if="attachment.type == 'binary'" t-set="attachment.url" t-value="'/base/binary/saveas?session_id='
+ session.session_id + '&amp;model=ir.attachment&amp;id=' + attachment.id
- + '&amp;field=datas' + '&amp;fieldname=name' + '&amp;t=' + (new Date().getTime())"/>
+ + '&amp;field=datas&amp;fieldname=name&amp;t=' + (new Date().getTime())"/>
<a class="oe-sidebar-attachments-link" t-att-href="attachment.url" target="_blank">
<t t-esc="attachment.name"/>
</a>
@@ -441,18 +438,21 @@
</t>
<t t-name="WidgetNotebook">
<ul>
- <li t-foreach="widget.pages" t-as="page">
- <a t-att-href="'#' + widget.element_id + '-' + page_index">
+ <li t-foreach="widget.pages" t-as="page" t-att-id="page.element_tab_id">
+ <a t-att-href="'#' + page.element_id">
<t t-esc="page.string"/>
</a>
</li>
</ul>
<t t-foreach="widget.pages" t-as="page">
- <div t-att-id="widget.element_id + '-' + page_index">
- <t t-raw="page.render()"/>
- </div>
+ <t t-raw="page.render()"/>
</t>
</t>
+<t t-name="WidgetNotebookPage">
+ <div t-att-id="widget.element_id">
+ <t t-call="WidgetFrame"/>
+ </div>
+</t>
<t t-name="WidgetSeparator">
<div t-att-class="'separator ' + (widget.node.attrs.orientation || 'horizontal')">
<t t-esc="widget.string"/>
@@ -526,9 +526,9 @@
t-att-id="widget.element_id + '_field'"
t-att-class="'field_' + widget.type"
style="width: 100%">
- <t t-foreach="widget.field.selection" t-as="options">
- <option t-att-value="options[0]">
- <t t-esc="options[1]"/>
+ <t t-foreach="widget.field_index" t-as="options">
+ <option t-att-value="options.ikey">
+ <t t-esc="options.label"/>
</option>
</t>
</select>
@@ -541,9 +541,9 @@
<span class="oe-m2o-cm-button" t-att-id="widget.name + '_open'">
<img src="/base/static/src/img/icons/gtk-index.png"/></span>
<div t-att-id="widget.cm_id" class="contextMenu" style="display:none"><ul>
- <li t-att-id="widget.cm_id + '_search'">Search</li>
- <li t-att-id="widget.cm_id + '_create'">Create New</li>
- <li t-att-id="widget.cm_id + '_open'" style="color:grey">Open</li>
+ <li t-att-id="widget.cm_id + '_open'" style="color:grey">Open...</li>
+ <li t-att-id="widget.cm_id + '_create'">Create...</li>
+ <li t-att-id="widget.cm_id + '_search'">Search...</li>
</ul></div>
</div>
</t>
@@ -685,14 +685,28 @@
<input type="reset" value="Clear"/>
<button class="oe_search-view-custom-filter-btn"><span>Advanced Filter</span></button>
<select class="oe_search-view-filters-management">
- <option value="_filters">-- Filters --</option>
- <option value="_actions">-- Actions --</option>
- <option value="save_filter">Save Filter</option>
- <option value="manage_filters">Manage Filters</option>
</select>
</div>
</form>
</t>
+<t t-name="SearchView.managed-filters">
+ <option value="_filters">-- Filters --</option>
+ <t t-set="i" t-value="0"/>
+ <t t-foreach="filters" t-as="filter">
+ <option t-att-value="'get:' + i"><t t-esc="filter.name"/></option>
+ <t t-set="i" t-value="i+1"/>
+ </t>
+ <option value="_actions">-- Actions --</option>
+ <option value="save_filter">Save Filter</option>
+ <option value="manage_filters">Manage Filters</option>
+</t>
+<t t-name="SearchView.managed-filters.add">
+ <div>
+ <p>Filter Name:</p>
+ <input type="text"/>
+ <p>(Any existing filter with the same name will be replaced)</p>
+ </div>
+</t>
<t t-name="SearchView.render_lines">
<table class="oe-searchview-render-line" border="0" cellspacing="0" cellpadding="0"
t-foreach="lines" t-as="line">
@@ -803,11 +817,11 @@
<t t-name="SearchView.extended_search.group">
<div t-att-id="element_id" class="searchview_extended_group">
<select class="searchview_extended_group_choice">
- <option value="all">All the following conditions must match</option>
<option value="any">Any of the following conditions must match</option>
+ <option value="all">All the following conditions must match</option>
<option value="none">None of the following conditions must match</option>
</select>
- <a class="searchview_extended_delete_group"
+ <a class="searchview_extended_delete_group"
href="javascript:void(0)"><span></span></a>
<div class="searchview_extended_propositions_list">
</div>
@@ -829,7 +843,7 @@
<select class="searchview_extended_prop_op"/>
<span class="searchview_extended_prop_value"/>
<a class="searchview_extended_delete_prop"
- href="javascript:void(0)"><span></span></a>
+ href="javascript:void(0)"><span> </span></a>
</div>
</t>
<t t-name="SearchView.extended_search.proposition.char">
@@ -856,29 +870,6 @@
</t>
</select>
</t>
-<t t-name="ViewManager.sidebar">
- <div t-att-id="element_id" class="sidebar-main-div closed-sidebar">
-
- </div>
-</t>
-<t t-name="ViewManager.sidebar.internal">
- <a class="toggle-sidebar"></a>
- <div class="sidebar-content">
- <div class="sidebar-attachments" t-if="view == 'form'"> </div>
- <div class="sidebar-actions">
- <t t-foreach="sidebar.sections" t-as="section" t-if="section.elements.length">
- <h2><t t-esc="section.label"/></h2>
- <ul>
- <li t-foreach="section.elements" t-as="element">
- <a class="oe_sidebar_action_a" t-attf-data-index="#{section_index}-#{element_index}" href="#">
- <t t-esc="element.name"/>
- </a>
- </li>
- </ul>
- </t>
- </div>
- </div>
-</t>
<t t-name="DialogWarning">
<table cellspacing="0" cellpadding="0" border="0" class="oe-dialog-warning">
<tr>
@@ -886,7 +877,9 @@
<td>
<p>
<t t-js="d">
- d.html_error = context.engine.tools.html_escape(d.error.data.fault_code).replace(/\n/g, '<br/>');
+ var message = d.message ? d.message : d.error.data.fault_code;
+ d.html_error = context.engine.tools.html_escape(message)
+ .replace(/\n/g, '<br/>');
</t>
<t t-raw="html_error"/>
</p>
@@ -901,9 +894,19 @@
</t>
<t t-name="SelectCreatePopup">
<div t-att-id="element_id">
- <div t-att-id="element_id + '_search'"></div>
- <div t-att-id="element_id + '_view_list'"></div>
- <div t-att-id="element_id + '_view_form'"></div>
+ <table style="width:100%">
+ <tr style="width:100%">
+ <td style="width:100%">
+ <div t-att-id="element_id + '_search'" style="width:100%"></div>
+ </td>
+ </tr>
+ <tr style="width:100%">
+ <td style="width:100%">
+ <div t-att-id="element_id + '_view_list'" style="width:100%"></div>
+ </td>
+ </tr>
+ </table>
+ <div t-att-id="element_id + '_view_form'" style="width:100%"></div>
</div>
</t>
<t t-name="SelectCreatePopup.search.buttons">
@@ -914,7 +917,15 @@
<button type="button" class="oe_selectcreatepopup-form-save">Save</button>
<button type="button" class="oe_selectcreatepopup-form-close">Close</button>
</t>
-
+<t t-name="FormOpenPopup">
+ <div t-att-id="element_id">
+ <div t-att-id="element_id + '_view_form'" style="width:100%"></div>
+ </div>
+</t>
+<t t-name="FormOpenPopup.form.buttons">
+ <button type="button" class="oe_formopenpopup-form-save">Save</button>
+ <button type="button" class="oe_formopenpopup-form-close">Close</button>
+</t>
<t t-name="ListView.row.frame" t-extend="WidgetFrame">
<t t-jquery="tr">
$(document.createElement('t'))
@@ -933,4 +944,145 @@
.unwrap();
</t>
</t>
+
+<t t-name="ExportView">
+ <a id="exportview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Export</a>
+</t>
+
+<t t-name="ExportTreeView">
+ <table class="view" style="background-color: #F3F3F3;">
+ <tr>
+ <td align="left">
+ This wizard will export all data that matches the current search criteria to a CSV file.
+ You can export all data or only the fields that can be reimported after modification.
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <table>
+ <tr>
+ <td class="label"><label>Export Type:</label></td>
+ <td>
+ <select id="import_compat" name="import_compat">
+ <option value="1">Import Compatible Export</option>
+ <option value="0">Export all Data</option>
+ </select>
+ </td>
+ <td class="label"><label>Export Format</label></td>
+ <td>
+ <select id="export_format" name="export_format">
+ <option value="csv">CSV</option>
+ <option value="xls">Excel</option>
+ </select>
+ </td>
+
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <table class="oe_export_fields_selector_export">
+ <tr>
+ <th class="oe_view_title" valign="bottom">Available fields</th>
+ <th class="oe_view_title"></th>
+ <th class="oe_view_title">Fields to export
+ <a style="color: blue; text-decoration: none;" href="#" id="export_new_list">Save fields list</a>
+ <div id="savenewlist"></div>
+ <div id="ExistsExportList"></div>
+ </th>
+ </tr>
+ <tr>
+ <td class="oe_export_fields_selector_left">
+ <div id="left_field_panel">
+ </div>
+ </td>
+ <td>
+ <table class="oe_export_fields_selector_center">
+ <tr>
+ <td align="center">
+ <button id="add_field" class="oe_export_button_export">Add</button>
+ </td>
+ </tr>
+ <tr>
+ <td align="center">
+ <button id="remove_field" class="oe_export_button_export">Remove</button>
+ </td>
+ </tr>
+ <tr>
+ <td align="center">
+ <button id="remove_all_field" class="oe_export_button_export">Remove All</button>
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td class="oe_export_fields_selector_right">
+ <select name="fields_list" id="fields_list" multiple="multiple"></select>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</t>
+
+<t t-name="ExportTreeView-Secondary">
+ <table id="field-tree-structure" class="oe_export_fields_selector_export" cellspacing="0" cellpadding="0">
+ <tr><th class="oe_export_tree_header"> Name </th></tr>
+ <t t-call="ExportTreeView-Secondary.children"/>
+ </table>
+</t>
+<t t-name="ExportTreeView-Secondary.children">
+ <t t-foreach="fields" t-as="field" >
+ <tr t-att-id="'treerow-' + field.id" class="oe_export_row">
+ <td>
+ <table class="tree_grid" border="0">
+ <tr class="oe_export_row">
+ <t t-foreach="(field.id).split('/')" t-as="level" >
+ <t t-if="(field.id).split('/')[0] != level">
+ <td width="18">&amp;nbsp;</td>
+ </t>
+ </t>
+ <td valign="top" align="left" style="cursor: pointer;" width="18">
+ <t t-if="(field.children).length >= 1">
+ <t t-if="(field.id).split('/').length != 3">
+ <img t-att-id="'parentimg-' + field.id" src="/base/static/src/img/expand.gif" width="16" height="16" border="0"/>
+ </t>
+ </t>
+ </td>
+ <td id="tree-column" valign="middle" align="left" style="cursor: pointer;">
+ <a t-att-id="'export-' + field.id" t-att-string="field.string" href="javascript: void(0);" style="text-decoration: none;">
+ <t t-esc="field.string"/>
+ </a>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </t>
+</t>
+
+<t t-name="ExportNewList">
+ <tr>
+ <th><label>Save as:</label></th>
+ <td><input size="10" type="text" id="savelist_name"/></td>
+ <td><button class="oe_export_button_export" id="add_export_list">Ok</button></td>
+ </tr>
+</t>
+
+<t t-name="Exists.ExportList">
+ <tr><th align="right"><label >Saved exports:</label></th></tr>
+ <tr align="left">
+ <td>
+ <select id="saved_export_list" style="width: 100%;">
+ <option></option>
+ <t t-foreach="existing_exports" t-as="export">
+ <option t-att-value="export.id"><t t-esc="export.name"/></option>
+ </t>
+ </select>
+ </td>
+ <td><button class="oe_export_button_export" id="delete_export_list">Delete</button></td>
+ </tr>
+</t>
</templates>