[MERGE]latest trunk-proto61.

bzr revid: kch@tinyerp.com-20110525055220-efvm850pbkjalouc
This commit is contained in:
Kunal Chavda (OpenERP)
2011-05-25 11:22:20 +05:30
22 changed files with 1289 additions and 875 deletions
+112 -109
View File
@@ -439,9 +439,12 @@ class DataSet(openerpweb.Controller):
class DataGroup(openerpweb.Controller):
_cp_path = "/base/group"
@openerpweb.jsonrequest
def read(self, request, model, group_by_fields, domain=None, context=None):
def read(self, request, model, group_by_fields, domain=None):
Model = request.session.model(model)
return Model.read_group(domain or False, False, group_by_fields, 0, False, context or False)
return Model.read_group(
domain or [], False, group_by_fields, 0, False,
dict(request.context, group_by=group_by_fields))
class View(openerpweb.Controller):
def fields_view_get(self, request, model, view_id, view_type,
@@ -555,6 +558,113 @@ class FormView(View):
fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
return {'fields_view': fields_view}
class ListView(View):
_cp_path = "/base/listview"
@openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False):
fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
return {'fields_view': fields_view}
def fields_view_get(self, request, model, view_id, view_type="tree",
transform=True, toolbar=False, submenu=False):
""" Sets @editable on the view's arch if it isn't already set and
``set_editable`` is present in the request context
"""
view = super(ListView, self).fields_view_get(
request, model, view_id, view_type, transform, toolbar, submenu)
view_attributes = view['arch']['attrs']
if request.context.get('set_editable')\
and 'editable' not in view_attributes:
view_attributes['editable'] = 'bottom'
return view
@openerpweb.jsonrequest
def fill(self, request, model, id, domain,
offset=0, limit=False, sort=None):
return self.do_fill(request, model, id, domain, offset, limit, sort)
def do_fill(self, request, model, id, domain,
offset=0, limit=False, sort=None):
""" Returns all information needed to fill a table:
* view with processed ``editable`` flag
* fields (columns) with processed ``invisible`` flag
* rows with processed ``attrs`` and ``colors``
.. note:: context is passed through ``request`` parameter
:param request: OpenERP request
:type request: openerpweb.openerpweb.JsonRequest
:type str model: OpenERP model for this list view
:type int id: view_id, or False if none provided
:param list domain: the search domain to search for
:param int offset: search offset, for pagination
:param int limit: search limit, for pagination
:returns: hell if I have any idea yet
"""
view = self.fields_view_get(request, model, id, toolbar=True)
rows = DataSet().do_search_read(request, model,
offset=offset, limit=limit,
domain=domain, sort=sort)
eval_context = request.session.evaluation_context(
request.context)
if sort:
sort_criteria = sort.split(',')[0].split(' ')
view['sorted'] = {
'field': sort_criteria[0],
'reversed': sort_criteria[1] == 'DESC'
}
else:
view['sorted'] = {}
return {
'view': view,
'records': [
{'data': dict((key, {'value': value})
for key, value in row.iteritems()),
'color': self.process_colors(view, row, eval_context)}
for row in rows
]
}
def process_colors(self, view, row, context):
colors = view['arch']['attrs'].get('colors')
if not colors:
return None
color = [
pair.split(':')[0]
for pair in colors.split(';')
if eval(pair.split(':')[1], dict(context, **row))
]
if not color:
return None
elif len(color) == 1:
return color[0]
return 'maroon'
class SearchView(View):
_cp_path = "/base/searchview"
@openerpweb.jsonrequest
def load(self, req, model, view_id):
fields_view = self.fields_view_get(req, model, view_id, 'search')
return {'fields_view': fields_view}
@openerpweb.jsonrequest
def fields_get(self, req, model):
Model = req.session.model(model)
fields = Model.fields_get()
return {'fields': fields}
class Binary(openerpweb.Controller):
_cp_path = "/base/binary"
@openerpweb.httprequest
def image(self, request, session_id, model, id, field, **kw):
cherrypy.response.headers['Content-Type'] = 'image/png'
@@ -612,113 +722,6 @@ class FormView(View):
args = [False, e.message]
return out % (simplejson.dumps(callback), simplejson.dumps(args))
class ListView(View):
_cp_path = "/base/listview"
@openerpweb.jsonrequest
def load(self, req, model, view_id, toolbar=False):
fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar)
return {'fields_view': fields_view}
def fields_view_get(self, request, model, view_id, view_type="tree",
transform=True, toolbar=False, submenu=False):
""" Sets @editable on the view's arch if it isn't already set and
``set_editable`` is present in the request context
"""
view = super(ListView, self).fields_view_get(
request, model, view_id, view_type, transform, toolbar, submenu)
view_attributes = view['arch']['attrs']
if request.context.get('set_editable')\
and 'editable' not in view_attributes:
view_attributes['editable'] = 'bottom'
return view
@openerpweb.jsonrequest
def fill(self, request, model, id, domain,
offset=0, limit=False, sort=None):
return self.do_fill(request, model, id, domain, offset, limit, sort)
def do_fill(self, request, model, id, domain,
offset=0, limit=False, sort=None):
""" Returns all information needed to fill a table:
* view with processed ``editable`` flag
* fields (columns) with processed ``invisible`` flag
* rows with processed ``attrs`` and ``colors``
.. note:: context is passed through ``request`` parameter
:param request: OpenERP request
:type request: openerpweb.openerpweb.JsonRequest
:type str model: OpenERP model for this list view
:type int id: view_id, or False if none provided
:param list domain: the search domain to search for
:param int offset: search offset, for pagination
:param int limit: search limit, for pagination
:returns: hell if I have any idea yet
"""
view = self.fields_view_get(request, model, id, toolbar=True)
print sort
rows = DataSet().do_search_read(request, model,
offset=offset, limit=limit,
domain=domain, sort=sort)
eval_context = request.session.evaluation_context(
request.context)
if sort:
sort_criteria = sort.split(',')[0].split(' ')
print sort, sort_criteria
view['sorted'] = {
'field': sort_criteria[0],
'reversed': sort_criteria[1] == 'DESC'
}
else:
view['sorted'] = {}
return {
'view': view,
'records': [
{'data': dict((key, {'value': value})
for key, value in row.iteritems()),
'color': self.process_colors(view, row, eval_context)}
for row in rows
]
}
def process_colors(self, view, row, context):
colors = view['arch']['attrs'].get('colors')
if not colors:
return None
color = [
pair.split(':')[0]
for pair in colors.split(';')
if eval(pair.split(':')[1], dict(context, **row))
]
if not color:
return None
elif len(color) == 1:
return color[0]
return 'maroon'
class SearchView(View):
_cp_path = "/base/searchview"
@openerpweb.jsonrequest
def load(self, req, model, view_id):
fields_view = self.fields_view_get(req, model, view_id, 'search')
return {'fields_view': fields_view}
@openerpweb.jsonrequest
def fields_get(self, req, model):
Model = req.session.model(model)
fields = Model.fields_get()
return {'fields': fields}
class Action(openerpweb.Controller):
_cp_path = "/base/action"
@@ -0,0 +1,304 @@
// Underscore.string
// (c) 2010 Esa-Matti Suuronen <esa-matti aet suuronen dot org>
// Underscore.strings is freely distributable under the terms of the MIT license.
// Documentation: https://github.com/edtsech/underscore.string
// Some code is borrowed from MooTools and Alexandru Marasteanu.
// Version 1.1.4
(function(){
// ------------------------- Baseline setup ---------------------------------
// Establish the root object, "window" in the browser, or "global" on the server.
var root = this;
var nativeTrim = String.prototype.trim;
function str_repeat(i, m) {
for (var o = []; m > 0; o[--m] = i);
return o.join('');
}
function defaultToWhiteSpace(characters){
if (characters) {
return _s.escapeRegExp(characters);
}
return '\\s';
}
var _s = {
isBlank: function(str){
return !!str.match(/^\s*$/);
},
capitalize : function(str) {
return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
},
chop: function(str, step){
step = step || str.length;
var arr = [];
for (var i = 0; i < str.length;) {
arr.push(str.slice(i,i + step));
i = i + step;
}
return arr;
},
clean: function(str){
return _s.strip(str.replace(/\s+/g, ' '));
},
count: function(str, substr){
var count = 0, index;
for (var i=0; i < str.length;) {
index = str.indexOf(substr, i);
index >= 0 && count++;
i = i + (index >= 0 ? index : 0) + substr.length;
}
return count;
},
chars: function(str) {
return str.split('');
},
escapeHTML: function(str) {
return String(str||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/"/g, '&quot;').replace(/'/g, "&apos;");
},
unescapeHTML: function(str) {
return String(str||'').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&quot;/g, '"').replace(/&apos;/g, "'");
},
escapeRegExp: function(str){
// From MooTools core 1.2.4
return String(str||'').replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
},
insert: function(str, i, substr){
var arr = str.split('');
arr.splice(i, 0, substr);
return arr.join('');
},
includes: function(str, needle){
return str.indexOf(needle) !== -1;
},
join: function(sep) {
// TODO: Could this be faster by converting
// arguments to Array and using array.join(sep)?
sep = String(sep);
var str = "";
for (var i=1; i < arguments.length; i += 1) {
str += String(arguments[i]);
if ( i !== arguments.length-1 ) {
str += sep;
}
}
return str;
},
lines: function(str) {
return str.split("\n");
},
// reverse: function(str){
// return Array.prototype.reverse.apply(str.split('')).join('');
// },
splice: function(str, i, howmany, substr){
var arr = str.split('');
arr.splice(i, howmany, substr);
return arr.join('');
},
startsWith: function(str, starts){
return str.length >= starts.length && str.substring(0, starts.length) === starts;
},
endsWith: function(str, ends){
return str.length >= ends.length && str.substring(str.length - ends.length) === ends;
},
succ: function(str){
var arr = str.split('');
arr.splice(str.length-1, 1, String.fromCharCode(str.charCodeAt(str.length-1) + 1));
return arr.join('');
},
titleize: function(str){
var arr = str.split(' '),
word;
for (var i=0; i < arr.length; i++) {
word = arr[i].split('');
if(typeof word[0] !== 'undefined') word[0] = word[0].toUpperCase();
i+1 === arr.length ? arr[i] = word.join('') : arr[i] = word.join('') + ' ';
}
return arr.join('');
},
camelize: function(str){
return _s.trim(str).replace(/(\-|_|\s)+(.)?/g, function(match, separator, chr) {
return chr ? chr.toUpperCase() : '';
});
},
underscored: function(str){
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/\-|\s+/g, '_').toLowerCase();
},
dasherize: function(str){
return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1-$2').replace(/^([A-Z]+)/, '-$1').replace(/\_|\s+/g, '-').toLowerCase();
},
trim: function(str, characters){
if (!characters && nativeTrim) {
return nativeTrim.call(str);
}
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), '');
},
ltrim: function(str, characters){
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('\^[' + characters + ']+', 'g'), '');
},
rtrim: function(str, characters){
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('[' + characters + ']+$', 'g'), '');
},
truncate: function(str, length, truncateStr){
truncateStr = truncateStr || '...';
return str.slice(0,length) + truncateStr;
},
words: function(str, delimiter) {
delimiter = delimiter || " ";
return str.split(delimiter);
},
pad: function(str, length, padStr, type) {
var padding = '';
var padlen = 0;
if (!padStr) { padStr = ' '; }
else if (padStr.length > 1) { padStr = padStr[0]; }
switch(type) {
case "right":
padlen = (length - str.length);
padding = str_repeat(padStr, padlen);
str = str+padding;
break;
case "both":
padlen = (length - str.length);
padding = {
'left' : str_repeat(padStr, Math.ceil(padlen/2)),
'right': str_repeat(padStr, Math.floor(padlen/2))
};
str = padding.left+str+padding.right;
break;
default: // "left"
padlen = (length - str.length);
padding = str_repeat(padStr, padlen);;
str = padding+str;
}
return str;
},
lpad: function(str, length, padStr) {
return _s.pad(str, length, padStr);
},
rpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'right');
},
lrpad: function(str, length, padStr) {
return _s.pad(str, length, padStr, 'both');
},
/**
* Credits for this function goes to
* http://www.diveintojavascript.com/projects/sprintf-for-javascript
*
* Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
* All rights reserved.
* */
sprintf: function(){
var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = '';
while (f) {
if (m = /^[^\x25]+/.exec(f)) {
o.push(m[0]);
}
else if (m = /^\x25{2}/.exec(f)) {
o.push('%');
}
else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) {
throw('Too few arguments.');
}
if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) {
throw('Expecting number but found ' + typeof(a));
}
switch (m[7]) {
case 'b': a = a.toString(2); break;
case 'c': a = String.fromCharCode(a); break;
case 'd': a = parseInt(a); break;
case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
case 'o': a = a.toString(8); break;
case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
case 'u': a = Math.abs(a); break;
case 'x': a = a.toString(16); break;
case 'X': a = a.toString(16).toUpperCase(); break;
}
a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a);
c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
x = m[5] - String(a).length - s.length;
p = m[5] ? str_repeat(c, x) : '';
o.push(s + (m[4] ? a + p : p + a));
}
else {
throw('Huh ?!');
}
f = f.substring(m[0].length);
}
return o.join('');
}
}
// Aliases
_s.strip = _s.trim;
_s.lstrip = _s.ltrim;
_s.rstrip = _s.rtrim;
_s.center = _s.lrpad
_s.ljust = _s.lpad
_s.rjust = _s.rpad
// CommonJS module is defined
if (typeof window === 'undefined' && typeof module !== 'undefined') {
// Export module
module.exports = _s;
// Integrate with Underscore.js
} else if (typeof root._ !== 'undefined') {
root._.mixin(_s);
// Or define it
} else {
root._ = _s;
}
}());
@@ -1,161 +0,0 @@
// Underscore.strings
// (c) 2010 Esa-Matti Suuronen <esa-matti aet suuronen dot org>
// Underscore.strings is freely distributable under the terms of the MIT license.
// Documentation: http://bitbucket.org/epeli/underscore.strings/
// Some code is borrowed from MooTools and Alexandru Marasteanu.
// Version 1.0
(function(){
// ------------------------- Baseline setup ---------------------------------
// Establish the root object, "window" in the browser, or "global" on the server.
var root = this;
var nativeTrim = String.prototype.trim;
function str_repeat(i, m) {
for (var o = []; m > 0; o[--m] = i);
return o.join('');
}
function defaultToWhiteSpace(characters){
if (characters) {
return _s.escapeRegExp(characters);
}
return '\\s';
}
var _s;
_s = root._s = {
capitalize : function(str) {
return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
},
join: function(sep) {
// TODO: Could this be faster by converting
// arguments to Array and using array.join(sep)?
sep = String(sep);
var str = "";
for (var i=1; i < arguments.length; i += 1) {
str += String(arguments[i]);
if ( i !== arguments.length-1 ) {
str += sep;
}
}
return str;
},
escapeRegExp: function(str){
// From MooTools core 1.2.4
return str.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
},
reverse: function(str){
return Array.prototype.reverse.apply(str.split('')).join('');
},
contains: function(str, needle){
return str.indexOf(needle) !== -1;
},
clean: function(str){
return _s.strip(str.replace(/\s+/g, ' '));
},
trim: function(str, characters){
if (!characters && nativeTrim) {
return nativeTrim.call(str);
}
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), '');
},
ltrim: function(str, characters){
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('\^[' + characters + ']+', 'g'), '');
},
rtrim: function(str, characters){
characters = defaultToWhiteSpace(characters);
return str.replace(new RegExp('[' + characters + ']+$', 'g'), '');
},
startsWith: function(str, starts){
return str.length >= starts.length && str.substring(0, starts.length) === starts;
},
endsWith: function(str, ends){
return str.length >= ends.length && str.substring(str.length - ends.length) === ends;
},
/**
* Credits for this function goes to
* http://www.diveintojavascript.com/projects/sprintf-for-javascript
*
* Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
* All rights reserved.
* */
sprintf: function(){
var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = '';
while (f) {
if (m = /^[^\x25]+/.exec(f)) {
o.push(m[0]);
}
else if (m = /^\x25{2}/.exec(f)) {
o.push('%');
}
else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) {
throw('Too few arguments.');
}
if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) {
throw('Expecting number but found ' + typeof(a));
}
switch (m[7]) {
case 'b': a = a.toString(2); break;
case 'c': a = String.fromCharCode(a); break;
case 'd': a = parseInt(a); break;
case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
case 'o': a = a.toString(8); break;
case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
case 'u': a = Math.abs(a); break;
case 'x': a = a.toString(16); break;
case 'X': a = a.toString(16).toUpperCase(); break;
}
a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a);
c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
x = m[5] - String(a).length - s.length;
p = m[5] ? str_repeat(c, x) : '';
o.push(s + (m[4] ? a + p : p + a));
}
else {
throw('Huh ?!');
}
f = f.substring(m[0].length);
}
return o.join('');
}
}
// Some aliases
root._s.strip = _s.trim;
root._s.lstrip = _s.ltrim;
root._s.rstrip = _s.rtrim;
// Integrate with Underscore.js
if (root._) {
root._.mixin(root._s);
}
}());
+4 -5
View File
@@ -7,7 +7,7 @@
<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.strings.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>
@@ -20,7 +20,7 @@
<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/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/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>
@@ -28,8 +28,9 @@
<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/tree.js"></script>
<script type="text/javascript" src="/base/static/src/js/search.js"></script>
<script type="text/javascript" src="/base/static/src/js/m2o.js"></script>
<script type="text/javascript" src="/base/static/src/js/m2o.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" />
@@ -51,5 +52,3 @@
<body id="oe" class="openerp">
</body>
</html>
+102 -45
View File
@@ -95,42 +95,64 @@ body.openerp {
.openerp .menu {
height: 34px;
background: url(../img/menu-bg.png) repeat-x;
background: #cc4e45; /* Old browsers */
background: -moz-linear-gradient(top, #cc4e45 0%, #b52d20 8%, #7a211a 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#cc4e45), color-stop(8%,#b52d20), color-stop(100%,#7a211a)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#CC4E45', endColorstr='#7A211A',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #cc4e45 0%,#b52d20 8%,#7a211a 100%); /* W3C */
}
.openerp .menu td {
text-align: center;
padding:0;
}
.openerp .menu a {
display: block;
padding: 0 0 0 3px;
background: url(../img/menu-item.png) no-repeat;
display:block;
min-width: 60px;
height: 20px;
margin: 3px 2px;
padding: 0 8px;
background: #bd5e54; /* Old browsers */
background: -moz-linear-gradient(top, #bd5e54 0%, #90322a 60%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bd5e54), color-stop(60%,#90322a)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #bd5e54 0%,#90322a 60%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #bd5e54 0%,#90322a 60%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #bd5e54 0%,#90322a 60%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#BD5E54', endColorstr='#90322A',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #bd5e54 0%,#90322a 60%); /* W3C */
border: 1px solid #6E2A24;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
color: #eee;
text-shadow: #222 0 1px 0;
text-decoration: none;
text-align: center;
text-transform: uppercase;
margin: 2px 1px;
height: 23px;
}
.openerp .menu span {
display: block;
min-width: 60px;
height: 23px;
padding: 0 9px 0 6px;
background: url(../img/menu-item.png) 100% 0 no-repeat;
line-height: 23px;
line-height: 20px;
font-weight: bold;
font-size: 85%;
}
.openerp .menu a:hover,
.openerp .menu a:focus,
.openerp .menu a.active {
background-position: 0 -23px;
background: #c6c6c6; /* Old browsers */
background: -moz-linear-gradient(top, #c6c6c6 0%, #5c5c5c 7%, #969595 86%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#c6c6c6), color-stop(7%,#5c5c5c), color-stop(86%,#969595)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#C6C6C6', endColorstr='#969595',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* W3C */
/* for ie */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#5c5c5c', endColorstr='#969595',GradientType=0 ); /* IE6-9 */
color: #fff;
}
.openerp .menu a:hover span,
.openerp .menu a:focus span,
.openerp .menu a.active span {
background-position: 100% -23px;
}
/* Secondary Menu */
.openerp .secondary_menu {
width: 200px;
@@ -149,8 +171,19 @@ body.openerp {
}
.openerp .secondary_menu h3 {
padding: 0 0 2px;
background: url(../img/secondary_menu-bg.gif) 0 100% repeat-x;
background: #949292; /* Old browsers */
background: -moz-linear-gradient(top, #949292 0%, #6d6b6b 87%, #282828 99%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#949292), color-stop(87%,#6d6b6b), color-stop(99%,#282828)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#282828',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #949292 0%,#6d6b6b 87%,#282828 99%); /* W3C */
/* for ie9 */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#5B5A5A',GradientType=0 ); /* IE6-9 */
border: none;
/* overriding jquery ui */
-moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px;
}
.openerp .secondary_menu h4 {
padding: 0 0 2px 10px;
@@ -170,19 +203,31 @@ body.openerp {
text-decoration: none;
text-shadow: 0 1px 0 #333;
}
.openerp .secondary_menu h3 a {
background: url(../img/secondary_menu-h3-bg.png) 0 100% repeat-x;
}
.openerp .secondary_menu a.leaf:hover,
.openerp .secondary_menu a.leaf:active,
.openerp .secondary_menu a.leaf.active,
.openerp .secondary_menu h4:hover,
.openerp .secondary_menu h4:active,
.openerp .secondary_menu h4.active,
.openerp .secondary_menu h4.active {
background: #ffffff; /* Old browsers */
background: -moz-linear-gradient(top, #ffffff 0%, #d8d8d8 11%, #afafaf 86%, #333333 91%, #5a5858 96%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(11%,#d8d8d8), color-stop(86%,#afafaf), color-stop(91%,#333333), color-stop(96%,#5a5858)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#5A5858',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,#5a5858 96%); /* W3C */
/* overriding jquery ui */
-moz-border-radius-topright: 0; -webkit-border-top-right-radius: 0; border-top-right-radius: 0;
color: #3f3d3d;
text-shadow: #fff 0 1px 0;
border: none !important;
}
.openerp .secondary_menu h4:hover a,
.openerp .secondary_menu h4:active a,
.openerp .secondary_menu h4.active a {
background: url(../img/secondary_menu-link-bg.gif) repeat-x;
color: #3f3d3d;
text-shadow: #fff 0 1px 0;
border: none !important;
@@ -522,12 +567,12 @@ body.openerp {
-moz-border-radius-topright: 7px;
border-top-right-radius: 7px;
}
.openerp .oe-listview table tbody tr:last-child th:first-child {
.openerp .oe-listview table 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 tbody tr:last-child td:last-child {
.openerp .oe-listview table 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;
@@ -628,7 +673,7 @@ body.openerp {
/* http://www.quirksmode.org/dom/inputfile.html
* http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image
*/
.openerp button.oe-binary-file-set {
.openerp .oe-binary-file-set {
overflow: hidden;
position: relative;
}
@@ -713,25 +758,25 @@ body.openerp {
}
.openerp .sidebar-main-div {
height:100%;
height: 100%;
border-left: 1px solid #D2CFCF;
}
.openerp .sidebar-sub-div {
padding:0;
margin:0;
width:180px;
border-left: 1px solid #D2CFCF;
height:100%;
.openerp .sidebar-content {
padding: 0;
margin: 0;
width: 180px;
height: 100%;
font-family: Ubuntu, Helvetica, sans-serif;
font-size: 0.9em;
}
.openerp .closed-sidebar .sidebar-sub-div {
width:22px;
.openerp .closed-sidebar .sidebar-content {
width: 22px;
}
.openerp .closed-sidebar .sidebar-displaying-div {
display:none;
.openerp .closed-sidebar .sidebar-content {
display: none;
}
.openerp .sidebar-main-div a {
@@ -752,7 +797,16 @@ body.openerp {
padding-left: 10px;
padding-right: 21px;
height: 21px;
background: url(../img/sideheader-a-bg.png) repeat-x;
background: #ffffff; /* Old browsers */
background: -moz-linear-gradient(top, #ffffff 0%, #ebe9e9 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#ebe9e9)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#EBE9E9',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */
border: 1px solid #D2CFCF;
border-right-width: 0;
border-left-width: 0;
@@ -777,12 +831,15 @@ body.openerp {
background: url(../img/toggle-a-bg.png);
width: 21px;
height: 21px;
position: absolute;
z-index: 10;
}
.openerp .open-sidebar .toggle-sidebar {
margin-left: 158px;
background-position: 21px 0;
position: absolute;
}
.openerp .closed-sidebar .toggle-sidebar {
border-left: none;
}
.openerp.kitten-mode-activated .main_table {
@@ -809,7 +866,7 @@ body.openerp {
.openerp.kitten-mode-activated .menu span {
background: none;
}
.openerp.kitten-mode-activated .sidebar-displaying-div li a,
.openerp.kitten-mode-activated .sidebar-content li a,
.openerp.kitten-mode-activated .oe-application .view-manager-main-content h2.oe_view_title,
.openerp.kitten-mode-activated .oe-application .view-manager-main-content a.searchview_group_string,
.openerp.kitten-mode-activated .oe-application .view-manager-main-content label {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 B

+1
View File
@@ -129,6 +129,7 @@ openerp.base = function(instance) {
openerp.base.views(instance);
openerp.base.search(instance);
openerp.base.list(instance);
openerp.base.tree(instance);
openerp.base.m2o(instance);
openerp.base.form(instance);
};
+26
View File
@@ -231,6 +231,32 @@ openerp.base.BasicController = Class.extend( /** @lends openerp.base.BasicContro
}
});
/**
* 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);
var init = openerp.base.BasicController.prototype.init;
newer.init = 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);
+137 -71
View File
@@ -22,23 +22,113 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas
* @param {Array} domain search domain for this DataGroup
* @param {Object} context context of the DataGroup's searches
* @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) {
init: function(session, model, domain, context, group_by, level) {
if (group_by) {
if (group_by.length || context['group_by_no_leaf']) {
return new openerp.base.ContainerDataGroup(
session, model, domain, context, group_by, level);
} else {
return new openerp.base.GrouplessDataGroup(
session, model, domain, context, level);
}
}
this._super(session, null);
this.model = model;
this.context = context;
this.domain = domain;
this.group_by = group_by;
this.level = level || 0;
},
cls: 'DataGroup'
});
openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend(
/** @lends openerp.base.ContainerDataGroup# */ {
/**
*
* @constructs
* @extends openerp.base.DataGroup
*
* @param session
* @param model
* @param domain
* @param context
* @param group_by
* @param level
*/
init: function (session, model, domain, context, group_by, level) {
this._super(session, model, domain, context, null, level);
this.groups = null;
this.group_by = group_by;
},
/**
* The format returned by ``read_group`` is absolutely dreadful:
*
* * A ``__context`` key provides future grouping levels
* * A ``__domain`` key provides the domain for the next search
* * The current grouping value is provided through the name of the
* current grouping name e.g. if currently grouping on ``user_id``, then
* the ``user_id`` value for this group will be provided through the
* ``user_id`` key.
* * Similarly, the number of items in the group (not necessarily direct)
* is provided via ``${current_field}_count``
* * Other aggregate fields are just dumped there
*
* This function slightly improves the grouping records by:
*
* * Adding a ``grouped_on`` property providing the current grouping field
* * Adding a ``value`` and a ``length`` properties which replace the
* ``$current_field`` and ``${current_field}_count`` ones
* * Moving aggregate values into an ``aggregates`` property object
*
* Context and domain keys remain as-is, they should not be used externally
* but in case they're needed...
*
* @param {Object} group ``read_group`` record
*/
transform_group: function (group) {
var field_name = this.group_by[0];
// In cases where group_by_no_leaf and no group_by, the result of
// read_group has aggregate fields but no __context or __domain.
// Create default (empty) values for those so that things don't break
var fixed_group = _.extend(
{__context: {group_by: []}, __domain: []},
group);
var aggregates = {};
_(fixed_group).each(function (value, key) {
if (key.indexOf('__') === 0
|| key === field_name
|| key === field_name + '_count') {
return;
}
aggregates[key] = value;
});
return {
__context: fixed_group.__context,
__domain: fixed_group.__domain,
grouped_on: field_name,
// if terminal group (or no group) and group_by_no_leaf => use group.__count
length: fixed_group[field_name + '_count'] || fixed_group.__count,
value: fixed_group[field_name],
openable: !(this.context['group_by_no_leaf']
&& fixed_group.__context.group_by.length === 0),
aggregates: aggregates
};
},
fetch: function () {
// internal method
var d = new $.Deferred();
var self = this;
if (this.groups) {
// 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', {
@@ -47,20 +137,10 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas
domain: this.domain,
group_by_fields: this.group_by
}, function () { }).then(function (response) {
self.groups = response.result;
// read_group results are annoying: they use the name of the
// field grouped on to hold the value and the count, so no
// generic access to those values is possible.
// Alias them to `value` and `length`.
d.resolveWith(self, [_(response.result).map(function (group) {
var field_name = self.group_by[0];
return _.extend({}, group, {
// provide field used for grouping
grouped_on: field_name,
length: group[field_name + '_count'],
value: group[field_name]
});
})]);
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]);
});
@@ -68,55 +148,7 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas
return d.promise();
},
/**
* Retrieves the content of an item in the DataGroup, which results in
* either a DataSet or new DataGroup instance.
*
* Calling :js:func:`~openerp.base.DataGroup.get` without having called
* :js:func:`~openerp.base.DataGroup.list` beforehand will likely result
* in an error.
*
* The resulting :js:class:`~openerp.base.DataGroup` or
* :js:class:`~openerp.base.DataSet` will be provided through the relevant
* callback function. In both functions, the current DataGroup will be
* provided as context (``this``)
*
* @param {Number} index the index of the group to open in the datagroup's collection
* @param {Function} ifDataSet executed if the item results in a DataSet, provided with the new dataset as parameter
* @param {Function} ifDataGroup executed if the item results in a DataSet, provided with the new datagroup as parameter
*/
get: function (index, ifDataSet, ifDataGroup) {
var group = this.groups[index];
if (!group) {
throw new Error("No group at index " + index);
}
var child_context = _.extend({}, this.context, group.__context);
if (group.__context.group_by.length) {
var datagroup = new openerp.base.DataGroup(
this.session, this.model, group.__domain, child_context,
group.__context.group_by);
ifDataGroup.call(this, datagroup);
} else {
var dataset = new openerp.base.DataSetSearch(this.session, this.model);
dataset.domain = group.__domain;
dataset.context = child_context;
ifDataSet.call(this, dataset);
}
},
/**
* Gathers the content of the current data group (the current grouping
* level), and provides it via a promise object to which callbacks should
* be added::
*
* datagroup.list().then(function (list) {
* // manipulate list here
* });
*
* The argument to the callback is the list of elements fetched, the
* context (``this``) is the datagroup itself.
*
* The items of a list are the standard objects returned by ``read_group``
* with three properties added:
* The items of a list have the following properties:
*
* ``length``
* the number of records contained in the group (and all of its
@@ -131,11 +163,44 @@ openerp.base.DataGroup = openerp.base.Controller.extend( /** @lends openerp.bas
* ``value``
* the value which led to this group (this is the value all contained
* records have for the current ``grouped_on`` field name).
*
* @returns {$.Deferred}
* ``aggregates``
* a mapping of other aggregation fields provided by ``read_group``
*/
list: function () {
return this.fetch();
list: function (ifGroups, ifRecords) {
var self = this;
this.fetch().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,
child_context, child_context.group_by,
self.level + 1),
group);
}));
});
}
});
openerp.base.GrouplessDataGroup = openerp.base.DataGroup.extend(
/** @lends openerp.base.GrouplessDataGroup# */ {
/**
*
* @constructs
* @extends openerp.base.DataGroup
*
* @param session
* @param model
* @param domain
* @param context
* @param level
*/
init: function (session, model, domain, context, level) {
this._super(session, model, domain, context, null, level);
},
list: function (ifGroups, ifRecords) {
ifRecords(_.extend(
new openerp.base.DataSetSearch(this.session, this.model),
{domain: this.domain, context: this.context}));
}
});
@@ -303,6 +368,7 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
offset: offset,
limit: limit
}, function (records) {
self.ids.splice(0, self.ids.length);
self.offset = offset;
self.count = records.length; // TODO: get real count
for (var i=0; i < records.length; i++ ) {
+31 -33
View File
@@ -17,7 +17,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
*/
init: function(view_manager, session, element_id, dataset, view_id) {
this._super(session, element_id);
this.view_manager = view_manager;
this.view_manager = view_manager || new openerp.base.NullViewManager();
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
@@ -29,12 +29,12 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.ready = false;
this.show_invalid = true;
this.touched = false;
this.flags = view_manager.action.flags || {};
this.flags = this.view_manager.action.flags || {};
},
start: function() {
//this.log('Starting FormView '+this.model+this.view_id)
return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id,
toolbar:!!this.view_manager.sidebar}, this.on_loaded);
toolbar:!!this.flags.sidebar}, this.on_loaded);
},
on_loaded: function(data) {
var self = this;
@@ -56,10 +56,8 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
this.$element.find('#' + this.element_id + '_header button.oe_form_button_cancel').click(this.do_cancel);
this.$element.find('#' + this.element_id + '_header button.oe_form_button_new').click(this.on_button_new);
// sidebar stuff
if (this.view_manager.sidebar) {
this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
}
this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
this.view_manager.sidebar.do_refresh.add_last(this.on_sidebar_refreshed);
},
do_show: function () {
var self = this;
@@ -72,9 +70,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
} else {
this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
}
if (this.view_manager && this.view_manager.sidebar) {
this.view_manager.sidebar.refresh(true);
}
this.view_manager.sidebar.do_refresh(true);
},
do_hide: function () {
this.$element.hide();
@@ -307,6 +303,10 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV
do_cancel: function () {
this.notification.notify("Cancelling form");
},
on_sidebar_refreshed: function(new_view) {
var sidebar = this.view_manager.sidebar;
// TODO: Add attachment WIP
},
reload: function() {
if (this.datarecord.id) {
this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
@@ -782,7 +782,7 @@ openerp.base.form.FieldFloatTime = openerp.base.form.FieldChar.extend({
this._super.apply(this, arguments);
if (!this.invalid) {
var time = this.value.split(':');
this.value = parseInt(time[0], 10) + parseInt(time[1], 10) / 60;
this.set_value(parseInt(time[0], 10) + parseInt(time[1], 10) / 60);
}
}
}
@@ -913,12 +913,6 @@ openerp.base.form.FieldMany2OneDatasSet = openerp.base.DataSetStatic.extend({
}
});
openerp.base.form.FieldMany2OneViewManager = openerp.base.ViewManager.extend({
init: function(session, element_id, dataset, views) {
this._super(session, element_id, dataset, views);
}
});
openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
@@ -929,8 +923,7 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
this.$element = $('#' + this.element_id);
this.dataset = new openerp.base.form.FieldMany2OneDatasSet(this.session, this.field.relation);
var views = [ [false,"list"], [false,"form"] ];
this.viewmanager = new openerp.base.form.FieldMany2OneViewManager(this.view.session, this.element_id, this.dataset, views);
new openerp.base.m2o(this.viewmanager, this.$element, this.field.relation, this.dataset, this.session)
new openerp.base.m2o(this.$element, this.field.relation, this.dataset, this.session)
this.$element.find('input').change(this.on_ui_change);
},
set_value: function(value) {
@@ -968,6 +961,7 @@ openerp.base.form.FieldOne2ManyDatasSet = openerp.base.DataSetStatic.extend({
openerp.base.form.FieldOne2ManyViewManager = openerp.base.ViewManager.extend({
init: function(session, element_id, dataset, views) {
this._super(session, element_id, dataset, views);
this.action = {flags:{}};
}
});
@@ -991,7 +985,6 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
this.log("o2m.set_value",value);
this.viewmanager.dataset.ids = value;
this.viewmanager.dataset.count = value.length;
this.viewmanager.views.list.controller.do_update();
}
},
get_value: function(value) {
@@ -1017,8 +1010,10 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
start: function() {
this._super.apply(this, arguments);
this.dataset = new openerp.base.DataSetMany2Many(this.session, this.field.relation);
this.list_view = new openerp.base.form.Many2ManyListView(undefined, this.view.session,
this.list_id, this.dataset, false, {'selected': false, 'addable': 'Add'});
//TODO: switch to non selectable once xmo has corrected the bug related to that
this.list_view = new openerp.base.form.Many2ManyListView(null, this.view.session,
this.list_id, this.dataset, false, {'selectable-': false,
'addable': 'Add'});
var self = this;
this.list_view.m2m_field = this;
this.list_view.start();
@@ -1126,8 +1121,8 @@ openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({
if (this.searchview) {
this.searchview.stop();
}
this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search",
this.dataset, false, {});
this.searchview = new openerp.base.SearchView(null, this.session,
this.element_id + "_search", this.dataset, false, {});
this.searchview.on_search.add(function(domains, contexts, groupbys) {
self.view_list.do_search.call(
self, domains, contexts, groupbys);
@@ -1144,7 +1139,8 @@ openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({
self.stop();
});
self.view_list = new openerp.base.form.Many2XPopupListView( null, self.session,
self.element_id + "_view_list", self.dataset, false);
self.element_id + "_view_list", self.dataset, false,
{'deletable': false});
self.view_list.popup = self;
self.view_list.do_show();
self.view_list.start();
@@ -1165,7 +1161,7 @@ openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({
this.searchview.hide();
this.view_list.$element.hide();
this.dataset.index = null;
this.view_form = new openerp.base.FormView({}, this.session,
this.view_form = new openerp.base.FormView(null, this.session,
this.element_id + "_view_form", this.dataset, false);
this.view_form.start();
this.view_form.on_loaded.add_last(function() {
@@ -1191,7 +1187,7 @@ openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({
});
openerp.base.form.Many2XPopupListView = openerp.base.ListView.extend({
switch_to_record: function(index) {
select_record: function(index) {
this.popup.on_select_element(this.dataset.ids[index]);
}
});
@@ -1226,14 +1222,16 @@ openerp.base.form.FieldBinary = openerp.base.form.Field.extend({
}
return size.toFixed(2) + ' ' + units[i];
},
on_file_change: function() {
on_file_change: function(e) {
// TODO: on modern browsers, we could directly read the file locally on client ready to be used on image cropper
// http://www.html5rocks.com/tutorials/file/dndfiles/
// http://deepliquid.com/projects/Jcrop/demos.php?demo=handler
window[this.iframe] = this.on_file_uploaded;
this.$element.find('form.oe-binary-form input[name=session_id]').val(this.session.session_id);
this.$element.find('form.oe-binary-form').submit();
this.toggle_progress();
if ($(e.target).val() != '') {
this.$element.find('form.oe-binary-form input[name=session_id]').val(this.session.session_id);
this.$element.find('form.oe-binary-form').submit();
this.toggle_progress();
}
},
toggle_progress: function() {
this.$element.find('.oe-binary-progress, .oe-binary').toggle();
@@ -1253,7 +1251,7 @@ openerp.base.form.FieldBinary = openerp.base.form.Field.extend({
on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
},
on_save_as: function() {
var url = '/base/formview/saveas?session_id=' + this.session.session_id + '&model=' +
var url = '/base/binary/saveas?session_id=' + this.session.session_id + '&model=' +
this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name +
'&fieldname=' + (this.node.attrs.filename || '') + '&t=' + (new Date().getTime())
window.open(url);
@@ -1327,7 +1325,7 @@ openerp.base.form.FieldBinaryImage = openerp.base.form.FieldBinary.extend({
set_value: function(value) {
this._super.apply(this, arguments);
this.set_image_maxwidth();
var url = '/base/formview/image?session_id=' + this.session.session_id + '&model=' +
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);
}
+326 -152
View File
@@ -1,4 +1,5 @@
openerp.base.list = function (openerp) {
'use strict';
openerp.base.views.add('list', 'openerp.base.ListView');
openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListView# */ {
defaults: {
@@ -45,30 +46,21 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
init: function(view_manager, session, element_id, dataset, view_id, options) {
var self = this;
this._super(session, element_id);
this.view_manager = view_manager;
this.view_manager = view_manager || new openerp.base.NullViewManager();
this.dataset = dataset;
this.model = dataset.model;
this.view_id = view_id;
this.columns = [];
this.rows = [];
this.options = _.extend({}, this.defaults, options || {});
this.flags = {};
if (view_manager && view_manager.action) {
this.flags = view_manager.action.flags;
}
this.flags = this.view_manager.action.flags;
this.list = new openerp.base.ListView.List({
options: this.options,
columns: this.columns,
rows: this.rows
});
this.groups = new openerp.base.ListView.Groups({
this.groups = new openerp.base.ListView.Groups(this, {
options: this.options,
columns: this.columns
});
$([this.list, this.groups]).bind({
$(this.groups).bind({
'selected': function (e, selection) {
self.$element.find('#oe-list-delete')
.toggle(!!selection.length);
@@ -76,18 +68,26 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
'deleted': function (e, ids) {
self.do_delete(ids);
},
'action': function (e, action_name, id) {
'action': function (e, action_name, id, callback) {
var action = _.detect(self.columns, function (field) {
return field.name === action_name;
});
if (!action) { return; }
// TODO: not supposed to reload everything, I think
self.execute_action(
action, self.dataset, self.session.action_manager,
id, self.do_reload);
id, function () {
if (callback) {
callback();
}
});
},
'row_link': function (e, index) {
self.select_record(index);
'row_link': function (e, index, id, dataset) {
_.extend(self.dataset, {
domain: dataset.domain,
context: dataset.context
}).read_slice([], null, null, function () {
self.select_record(index);
});
}
});
@@ -103,7 +103,7 @@ 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,
toolbar: this.view_manager ? !!this.view_manager.sidebar : false
toolbar: !!this.flags.sidebar
}, this.on_loaded);
},
/**
@@ -127,41 +127,15 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
* @param {Object} data.fields_view fields_view_get result (processed)
* @param {Object} data.fields_view.fields mapping of fields for the current model
* @param {Object} data.fields_view.arch current list view descriptor
* @param {Array} columns columns to move to the front (and make visible)
*/
on_loaded: function(data) {
on_loaded: function(data, columns) {
var self = this;
this.fields_view = data.fields_view;
//this.log(this.fields_view);
this.name = "" + this.fields_view.arch.attrs.string;
var fields = this.fields_view.fields;
var domain_computer = openerp.base.form.compute_domain;
this.columns.splice(0, this.columns.length);
this.columns.push.apply(this.columns, _(this.fields_view.arch.children).chain()
.map(function (field) {
var name = field.attrs.name;
var column = _.extend({id: name, tag: field.tag},
field.attrs, fields[name]);
// attrs computer
if (column.attrs) {
var attrs = eval('(' + column.attrs + ')');
column.attrs_for = function (fields) {
var result = {};
for (var attr in attrs) {
result[attr] = domain_computer(attrs[attr], fields);
}
return result;
};
} else {
column.attrs_for = function () { return {}; };
}
return column;
}).value());
this.visible_columns = _.filter(this.columns, function (column) {
return column.invisible !== '1';
});
this.setup_columns(this.fields_view.fields, columns);
if (!this.fields_view.sorted) { this.fields_view.sorted = {}; }
@@ -181,49 +155,57 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
self.do_reload();
});
var $table = this.$element.find('table');
this.list.move_to($table);
// sidebar stuff
if (this.view_manager && this.view_manager.sidebar) {
this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
}
this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
},
/**
* Fills the table with the provided records after emptying it
* Sets up the listview's columns: merges view and fields data, move
* grouped-by columns to the front of the columns list and make them all
* visible.
*
* TODO: should also re-load the table itself, as e.g. columns may have changed
*
* @param {Object} result filling result
* @param {Array} [result.view] the new view (wrapped fields_view_get result)
* @param {Array} result.records records the records to fill the list view with
* @param {Object} fields fields_view_get's fields section
* @param {Array} groupby_columns columns the ListView is grouped by
*/
do_fill_table: function(result) {
if (result.view) {
this.on_loaded({fields_view: result.view});
}
var records = result.records;
setup_columns: function (fields, groupby_columns) {
var domain_computer = openerp.base.form.compute_domain;
this.rows.splice(0, this.rows.length);
this.rows.push.apply(this.rows, records);
// Keep current selected record, if it's still in our new search
var current_record_id = this.dataset.ids[this.dataset.index];
this.dataset.ids = _(records).chain().map(function (record) {
return record.data.id.value;
}).value();
this.dataset.index = _.indexOf(this.dataset.ids, current_record_id);
if (this.dataset.index < 0) {
this.dataset.index = 0;
}
var noop = function () { return {}; };
var field_to_column = function (field) {
var name = field.attrs.name;
var column = _.extend({id: name, tag: field.tag},
field.attrs, fields[name]);
// attrs computer
if (column.attrs) {
var attrs = eval('(' + column.attrs + ')');
column.attrs_for = function (fields) {
var result = {};
for (var attr in attrs) {
result[attr] = domain_computer(attrs[attr], fields);
}
return result;
};
} else {
column.attrs_for = noop;
}
return column;
};
this.dataset.count = this.dataset.ids.length;
var results = this.rows.length;
this.$element.find('table')
.find('.oe-pager-last').text(results).end()
.find('.oe-pager-total').text(results);
this.columns.splice(0, this.columns.length);
this.columns.push.apply(
this.columns,
_(this.fields_view.arch.children).map(field_to_column));
if (groupby_columns) {
this.columns.unshift({
id: '_group', tag: '', string: "Group", meta: true,
attrs_for: function () { return {}; }
}, {
id: '_count', tag: '', string: '#', meta: true,
attrs_for: function () { return {}; }
});
}
this.list.refresh();
this.visible_columns = _.filter(this.columns, function (column) {
return column.invisible !== '1';
});
},
/**
* Used to handle a click on a table row, if no other handler caught the
@@ -252,12 +234,11 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
do_show: function () {
this.$element.show();
if (this.hidden) {
this.do_reload();
this.$element.find('table').append(
this.groups.apoptosis().render());
this.hidden = false;
}
if (this.view_manager && this.view_manager.sidebar) {
this.view_manager.sidebar.refresh(true);
}
this.view_manager.sidebar.do_refresh(true);
},
do_hide: function () {
this.$element.hide();
@@ -265,12 +246,13 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
},
/**
* Reloads the search view based on the current settings (dataset & al)
*
* @param {Array} [primary_columns] columns to bring to the front of the
* sequence
*/
do_reload: function () {
// TODO: need to do 5 billion tons of pre-processing, bypass
// DataSet for now
//self.dataset.read_slice(self.dataset.fields, 0, self.limit,
// self.do_fill_table);
do_reload: function (primary_columns) {
// TODO: should just fields_view_get I think
var self = this;
this.dataset.offset = 0;
this.dataset.limit = false;
return this.rpc('/base/listview/fill', {
@@ -279,7 +261,11 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
'context': this.dataset.context,
'domain': this.dataset.domain,
'sort': this.dataset.sort && this.dataset.sort()
}, this.do_fill_table);
}, function (result) {
if (result.view) {
self.on_loaded({fields_view: result.view}, primary_columns);
}
});
},
/**
* Event handler for a search, asks for the computation/folding of domains
@@ -297,24 +283,21 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
contexts: contexts,
group_by_seq: groupbys
}, function (results) {
// TODO: handle non-empty results.group_by with read_group
self.dataset.context = results.context;
self.dataset.domain = results.domain;
if (results.group_by.length) {
self.groups.datagroup = new openerp.base.DataGroup(
self.session, self.dataset.model,
results.domain, results.context,
results.group_by);
self.$element.html(self.groups.render());
return;
self.groups.datagroup = new openerp.base.DataGroup(
self.session, self.dataset.model,
results.domain, results.context,
results.group_by);
if (_.isEmpty(results.group_by) && !results.context['group_by_no_leaf']) {
results.group_by = null;
}
return self.do_reload();
self.do_reload(results.group_by).then(function () {
self.$element.find('table').append(self.groups.render());
});
});
},
do_update: function () {
var self = this;
//self.dataset.read_ids(self.dataset.ids, self.dataset.fields, self.do_fill_table);
},
/**
* Handles the signal to delete a line from the DOM
*
@@ -345,7 +328,6 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
self.rows.splice(record.index, 1);
});
// TODO only refresh modified rows
self.list.refresh();
});
},
/**
@@ -362,8 +344,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
* Handles deletion of all selected lines
*/
do_delete_selected: function () {
this.do_delete(
this.list.get_selection());
this.do_delete(this.groups.get_selection());
}
// TODO: implement reorder (drag and drop rows)
});
@@ -401,6 +382,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
this.options = opts.options;
this.columns = opts.columns;
this.dataset = opts.dataset;
this.rows = opts.rows;
this.$_element = $('<tbody class="ui-widget-content">')
@@ -427,21 +409,16 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
$(self).trigger(
'row_link',
[self.row_position(e.currentTarget),
self.row_id(e.currentTarget)]);
self.row_id(e.currentTarget),
self.dataset]);
});
},
move_to: function (element) {
this.$current = this.$_element.clone(true).appendTo(element);
this.render();
return this;
},
render: function () {
if (this.$current) {
this.$current.remove();
}
this.$current = this.$_element.clone(true);
this.$current.empty().append($(QWeb.render('ListView.rows', this)));
return this;
},
refresh: function () {
this.render();
return this;
},
/**
* Gets the ids of all currently selected records, if any
@@ -475,6 +452,14 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List
*/
row_id: function (row) {
return this.rows[this.row_position(row)].data.id.value;
},
/**
* Death signal, cleans up list
*/
apoptosis: function () {
if (!this.$current) { return; }
this.$current.remove();
this.$current = null;
}
// drag and drop
// editable?
@@ -487,44 +472,233 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
* Provides events similar to those of
* :js:class:`~openerp.base.ListView.List`
*/
init: function (opts) {
init: function (view, opts) {
this.view = view;
this.options = opts.options;
this.columns = opts.columns;
this.datagroup = {};
this.sections = [];
this.children = {};
},
make_level: function (datagroup) {
var self = this, $root = $('<dl>');
datagroup.list().then(function (list) {
_(list).each(function (group, index) {
var $title = $('<dt>')
.text(group.grouped_on + ': ' + group.value + ' (' + group.length + ')')
.appendTo($root);
$title.click(function () {
datagroup.get(index, function (new_dataset) {
var $content = $('<ul>').appendTo(
$('<dd>').insertAfter($title));
new_dataset.read_slice([], null, null, function (records) {
_(records).each(function (record) {
$('<li>')
.appendTo($content)
.text(_(record).map(function (value, key) {
return key + ': ' + value;
}).join(', '));
});
});
}, function (new_datagroup) {
console.log(new_datagroup);
$('<dd>')
.insertAfter($title)
.append(self.make_level(new_datagroup));
});
});
});
pad: function ($row) {
if (this.options.selectable) {
$row.append('<td>');
}
},
make_fragment: function () {
return document.createDocumentFragment();
},
/**
* Returns a DOM node after which a new tbody can be inserted, so that it
* follows the provided row.
*
* Necessary to insert the result of a new group or list view within an
* existing groups render, without losing track of the groups's own
* elements
*
* @param {HTMLTableRowElement} row the row after which the caller wants to insert a body
* @returns {HTMLTableSectionElement} element after which a tbody can be inserted
*/
point_insertion: function (row) {
var $row = $(row);
var red_letter_tbody = $row.closest('tbody')[0];
var $next_siblings = $row.nextAll();
if ($next_siblings.length) {
var $root_kanal = $('<tbody>').insertAfter(red_letter_tbody);
$root_kanal.append($next_siblings);
this.elements.splice(
_.indexOf(this.elements, red_letter_tbody),
0,
$root_kanal[0]);
}
return red_letter_tbody;
},
open_group: function (e, group) {
var row = e.currentTarget;
if (this.children[group.value]) {
this.children[group.value].apoptosis();
delete this.children[group.value];
}
var prospekt = this.children[group.value] = new openerp.base.ListView.Groups(this.view, {
options: this.options,
columns: this.columns
});
return $root;
this.bind_child_events(prospekt);
prospekt.datagroup = group;
prospekt.render().insertAfter(
this.point_insertion(row));
$(row).find('span.ui-icon')
.removeClass('ui-icon-triangle-1-e')
.addClass('ui-icon-triangle-1-s');
},
/**
* Prefixes ``$node`` with floated spaces in order to indent it relative
* to its own left margin/baseline
*
* @param {jQuery} $node jQuery object to indent
* @param {Number} level current nesting level, >= 1
* @returns {jQuery} the indentation node created
*/
indent: function ($node, level) {
return $('<span>')
.css({'float': 'left', 'white-space': 'pre'})
.text(new Array(level).join(' '))
.prependTo($node);
},
render_groups: function (datagroups) {
var self = this;
var placeholder = this.make_fragment();
_(datagroups).each(function (group) {
var $row = $('<tr>');
if (group.openable) {
$row.click(function (e) {
if (!$row.data('open')) {
$row.data('open', true);
self.open_group(e, group);
} else {
$row.removeData('open')
.find('span.ui-icon')
.removeClass('ui-icon-triangle-1-s')
.addClass('ui-icon-triangle-1-e');
_(self.children).each(function (child) {child.apoptosis();});
}
});
}
placeholder.appendChild($row[0]);
var $group_column = $('<th>').appendTo($row);
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));
if (group.openable) {
// Make openable if not terminal group & group_by_no_leaf
$group_column
.prepend('<span class="ui-icon ui-icon-triangle-1-e" style="float: left;">');
}
}
self.indent($group_column, group.level);
// count column
$('<td>').text(group.length).appendTo($row);
self.pad($row);
_(self.columns).chain()
.filter(function (column) {return !column.invisible;})
.each(function (column) {
if (column.meta) {
// do not do anything
} else if (column.id in group.aggregates) {
var value = group.aggregates[column.id];
var format;
if (column.type === 'integer') {
format = "%.0f";
} else if (column.type === 'float') {
format = "%.2f";
}
$('<td>')
.text(_.sprintf(format, value))
.appendTo($row);
} else {
$row.append('<td>');
}
});
});
return placeholder;
},
bind_child_events: function (child) {
var $this = $(this),
self = this;
$(child).bind('selected', function (e) {
// can have selections spanning multiple links
$this.trigger(e, [self.get_selection()]);
}).bind('action', function (e, name, id, callback) {
if (!callback) {
callback = function () {
var $prev = child.$current.prev();
if (!$prev.is('tbody')) {
// ungrouped
$(self.elements[0]).replaceWith(self.render());
} else {
// ghetto reload child (and its siblings)
$prev.children().last().click();
}
};
}
$this.trigger(e, [name, id, callback]);
}).bind('deleted row_link', function (e) {
// additional positional parameters are provided to trigger as an
// Array, following the event type or event object, but are
// provided to the .bind event handler as *args.
// Convert our *args back into an Array in order to trigger them
// on the group itself, so it can ultimately be forwarded wherever
// it's supposed to go.
var args = Array.prototype.slice.call(arguments, 1);
$this.trigger.call($this, e, args);
});
},
render_dataset: function (dataset) {
var rows = [],
list = new openerp.base.ListView.List({
options: this.options,
columns: this.columns,
dataset: dataset,
rows: rows
});
this.bind_child_events(list);
var d = new $.Deferred();
this.view.rpc('/base/listview/fill', {
model: dataset.model,
id: this.view.view_id,
context: dataset.context,
domain: dataset.domain,
sort: dataset.sort && dataset.sort()
}, function (result) {
rows.splice(0, rows.length);
rows.push.apply(rows, result.records);
list.render();
d.resolve(list);
});
return d.promise();
},
render: function () {
return this.make_level(this.datagroup);
var self = this;
var $element = $('<tbody>');
this.elements = [$element[0]];
this.datagroup.list(function (groups) {
$element[0].appendChild(
self.render_groups(groups));
}, function (dataset) {
self.render_dataset(dataset).then(function (list) {
self.children[null] = list;
self.elements =
[list.$current.replaceAll($element)[0]];
});
});
return $element;
},
/**
* Returns the ids of all selected records for this group
*/
get_selection: function () {
return _(this.children).chain()
.map(function (child) {
return child.get_selection();
})
.flatten()
.value();
},
apoptosis: function () {
_(this.children).each(function (child) {
child.apoptosis();
});
$(this.elements).remove();
return this;
}
});
};
+3 -3
View File
@@ -1,10 +1,9 @@
openerp.base.m2o = function(openerp){
openerp.base.m2o = openerp.base.Controller.extend({
init: function(view_manager, element_id, model, dataset, session){
init: function(element_id, model, dataset, session){
this._super(element_id, model, dataset, session);
this.view_manager = view_manager
this.session = session;
this.element = element_id.find('input');
this.button = element_id.find('div');
@@ -74,7 +73,8 @@ openerp.base.m2o = function(openerp){
minWidth: 800
});
self.element.val('');
var event_form = new openerp.base.FormView(self.view_manager, self.session, element_id, self.dataset, false);
var event_form = new openerp.base.FormView(null,
self.session, element_id, self.dataset, false);
event_form.start();
});
$input.val(self.element.data( "autocomplete" ).term);
@@ -2,7 +2,7 @@
* OpenERP base library
*---------------------------------------------------------*/
openerp.base.view_tree = function(openerp) {
openerp.base.tree = function(openerp) {
openerp.base.views.add('tree', 'openerp.base.TreeView');
openerp.base.TreeView = openerp.base.Controller.extend({
+23 -15
View File
@@ -21,7 +21,7 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
do_action: function(action) {
var self = this;
action.flags = _.extend({
sidebar : true,
sidebar : action.target != 'new',
search_view : true,
new_window : false,
views_switcher : true,
@@ -82,6 +82,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
this.views_src = views;
this.views = {};
this.flags = this.flags || {};
this.sidebar = new openerp.base.NullSidebar();
},
/**
* @returns {jQuery.Deferred} initial view loading promise
@@ -198,6 +199,14 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
}
});
openerp.base.NullViewManager = openerp.base.generate_null_object_class(openerp.base.ViewManager, {
init: function() {
this._super();
this.action = {flags: {}};
this.sidebar = new openerp.base.NullSidebar();
}
});
openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
init: function(session, element_id, action) {
var dataset;
@@ -213,7 +222,7 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
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.action_buttons = false;
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);
@@ -223,7 +232,7 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
var inital_view_loaded = this._super();
// init sidebar
if (this.sidebar) {
if (this.flags.sidebar) {
this.$element.find('.view-manager-main-sidebar').html(this.sidebar.render());
this.sidebar.start();
}
@@ -250,9 +259,7 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
},
stop: function() {
// should be replaced by automatic destruction implemented in BaseWidget
if (this.sidebar) {
this.sidebar.stop();
}
this.sidebar.stop();
this._super();
},
/**
@@ -295,9 +302,9 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
var section = {elements:toolbar[type[0]], label:type[1]};
self.sections.push(section);
});
this.refresh(true);
this.do_refresh(true);
},
refresh: function(new_view) {
do_refresh: function(new_view) {
var view = this.view_manager.active_view;
the_condition = this.sections.length > 0 && _.detect(this.sections,
function(x) {return x.elements.length > 0;}) != undefined
@@ -309,21 +316,20 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
this.$element.addClass('open-sidebar');
this.$element.removeClass('closed-sidebar');
}
this.$element.html(QWeb.render("ViewManager.sidebar.internal",this));
var self = this;
this.$element.find(".toggle-sidebar").click(function(e) {
self.$element.toggleClass('open-sidebar closed-sidebar');
e.stopPropagation();
e.preventDefault();
});
this.$element.find("a.oe_sidebar_action_a").click(function(e) {
var $this = jQuery(this);
var i = $this.attr("data-i");
var j = $this.attr("data-j");
var action = self.sections[i].elements[j];
var index = $this.attr("data-index").split('-');
var action = self.sections[index[0]].elements[index[1]];
action.flags = {
new_window : true
}
@@ -334,10 +340,12 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
},
start: function() {
this._super();
this.refresh(false);
this.do_refresh(false);
}
});
openerp.base.NullSidebar = openerp.base.generate_null_object_class(openerp.base.Sidebar);
openerp.base.View = openerp.base.Controller.extend({
/**
* Fetches and executes the action identified by ``action_data``.
+79 -59
View File
@@ -92,7 +92,7 @@
<t t-foreach="data.children" t-as="menu">
<td>
<a href="#" t-att-data-menu="menu.id">
<span><t t-esc="menu.name"/></span>
<t t-esc="menu.name"/>
</a>
</td>
</t>
@@ -147,7 +147,7 @@
<div t-attf-id="#{prefix}_view_#{view[1]}"/>
</t>
</td>
<td class="view-manager-main-sidebar">
<td class="view-manager-main-sidebar" height="100%">
</td>
</tr>
</table>
@@ -187,9 +187,14 @@
</th>
</tr>
<tr t-if="options.header">
<t t-foreach="columns" t-as="column">
<th t-if="column.meta">
<t t-esc="column.string"/>
</th>
</t>
<th t-if="options.selectable"/>
<t t-foreach="columns" t-as="column">
<th t-if="column.invisible !== '1'" t-att-data-id="column.id"
<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"/>
@@ -210,17 +215,22 @@
</t>
</t>
<tr t-name="ListView.row" t-att-style="style" t-att-class="row_parity">
<t t-foreach="columns" t-as="column">
<td t-if="column.meta">
</td>
</t>
<th t-if="options.selectable" class="oe-record-selector">
<input type="checkbox"/>
</th>
<t t-foreach="columns" t-as="column">
<t t-set="attrs" t-value="column.attrs_for(row.data)"/>
<td t-if="column.invisible !== '1'" t-att-title="column.help"
<td t-if="!column.meta and column.invisible !== '1'" t-att-title="column.help"
class="oe-field-cell" 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">
<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>
@@ -416,22 +426,28 @@
<tr>
<td align="center" valign="bottom" height="25">
<div class="oe-binary">
<!-- TODO: input@type=file inside a button won't work in firefox. Use links instead -->
<button class="button oe-binary-file-set" type="button" title="Set Image">
<img src="/base/static/src/img/icons/STOCK_DIRECTORY.png"/>
<form class="oe-binary-form" t-att-target="widget.iframe"
method="post" enctype="multipart/form-data" action="/base/formview/upload">
<input type="hidden" name="session_id" value=""/>
<input type="hidden" name="callback" t-att-value="widget.iframe"/>
<input type="file" class="oe-binary-file" name="ufile"/>
</form>
</button>
<button class="button oe-binary-file-save" type="button" title="Save As">
<img src="/base/static/src/img/icons/gtk-save.png"/>
</button>
<button class="button oe-binary-file-clear" type="button" title="Clear">
<img src="/base/static/src/img/icons/STOCK_MISSING_IMAGE.png"/>
</button>
<table cellspacing="0" cellpadding="0" border="0">
<tr>
<td>
<div class="oe-binary-file-set" style="width: 40px; height:22px;">
<form class="oe-binary-form" t-att-target="widget.iframe"
method="post" enctype="multipart/form-data" action="/base/binary/upload">
<input type="hidden" name="session_id" value=""/>
<input type="hidden" name="callback" t-att-value="widget.iframe"/>
<button class="button" type="button" title="Set Image">
<img src="/base/static/src/img/icons/STOCK_DIRECTORY.png"/>
</button>
<input type="file" class="oe-binary-file" name="ufile"/>
</form>
</div>
</td>
<td>
<button class="button oe-binary-file-clear" type="button" title="Clear">
<img src="/base/static/src/img/icons/STOCK_MISSING_IMAGE.png"/>
</button>
</td>
</tr>
</table>
</div>
<div class="oe-binary-progress" style="display: none">
<img src="/base/static/src/img/throbber.gif" width="16" height="16"/>
@@ -453,25 +469,36 @@
/>
</td>
<td class="oe-binary" nowrap="true">
<!-- TODO: input@type=file inside a button won't work in firefox. Use links instead -->
<button class="button oe-binary-file-set" type="button" title="Select file">
<img src="/base/static/src/img/icons/STOCK_DIRECTORY.png"/>
<span>Select</span>
<form class="oe-binary-form" t-att-target="widget.iframe"
method="post" enctype="multipart/form-data" action="/base/formview/upload">
<input type="hidden" name="session_id" value=""/>
<input type="hidden" name="callback" t-att-value="widget.iframe"/>
<input type="file" class="oe-binary-file" name="ufile"/>
</form>
</button>
<button class="button oe-binary-file-save" type="button" title="Save As">
<img src="/base/static/src/img/icons/gtk-save.png"/>
<span>Save As</span>
</button>
<button class="button oe-binary-file-clear" type="button" title="Clear">
<img src="/base/static/src/img/icons/STOCK_MISSING_IMAGE.png"/>
<span>Clear</span>
</button>
<table cellspacing="0" cellpadding="0" border="0">
<tr>
<td>
<div class="oe-binary-file-set" style="width: 80px; height:22px;">
<form class="oe-binary-form" t-att-target="widget.iframe"
method="post" enctype="multipart/form-data" action="/base/binary/upload">
<input type="hidden" name="session_id" value=""/>
<input type="hidden" name="callback" t-att-value="widget.iframe"/>
<button class="button" type="button" title="Set Image">
<img src="/base/static/src/img/icons/STOCK_DIRECTORY.png"/>
<span>Select</span>
</button>
<input type="file" class="oe-binary-file" name="ufile"/>
</form>
</div>
</td>
<td>
<button class="button oe-binary-file-save" type="button" title="Save As">
<img src="/base/static/src/img/icons/gtk-save.png"/>
<span>Save As</span>
</button>
</td>
<td>
<button class="button oe-binary-file-clear" type="button" title="Clear">
<img src="/base/static/src/img/icons/STOCK_MISSING_IMAGE.png"/>
<span>Clear</span>
</button>
</td>
</tr>
</table>
</td>
<td class="oe-binary-progress" style="display: none" nowrap="true">
<img src="/base/static/src/img/throbber.gif" width="16" height="16"/>
@@ -671,24 +698,17 @@
</t>
<t t-name="ViewManager.sidebar.internal">
<a class="toggle-sidebar"></a>
<div t-att-id="element_id" class="sidebar-sub-div">
<div class="sidebar-displaying-div">
<t t-set="i" t-value="1-1"/> <!-- al do stupid things -->
<t t-foreach="sections" t-as="section">
<t t-if="section.elements.length &gt; 0">
<h2><t t-esc="section.label"/></h2>
<ul>
<t t-set="j" t-value="1-1"/>
<t t-foreach="section.elements" t-as="element">
<li><a class="oe_sidebar_action_a" t-att-data-i="i" t-att-data-j="j" href="#">
<t t-esc="element.name"/></a></li>
<t t-set="j" t-value="j+1"/>
</t>
</ul>
</t>
<t t-set="i" t-value="i+1"/>
</t>
</div>
<div class="sidebar-content">
<t t-foreach="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>
</t>
<t t-name="DialogWarning">
+60 -57
View File
@@ -18,6 +18,15 @@ $(document).ready(function () {
}
}};
var fuck_that_shit = {
action: {
flags: {}
},
sidebar: {
set_toolbar: function () {}
}
};
var openerp;
module("ListView", {
setup: function () {
@@ -31,28 +40,27 @@ $(document).ready(function () {
}
});
asyncTest('render selection checkboxes', 2, function () {
test('render selection checkboxes', 2, function () {
var listview = new openerp.base.ListView(
{}, null,
fuck_that_shit, null,
'qunit-fixture', {model: null, ids: [null, null, null], index: 0});
listview.on_loaded(fvg);
listview.do_fill_table({records: [
{data: {id: {value: null}}},
{data: {id: {value: null}}},
{data: {id: {value: null}}}
]}).then(function () {
ok(are(listview.$element.find('tbody th'),
'.oe-record-selector'));
ok(are(listview.$element.find('tbody th input'),
':checkbox:not([name])'));
start();
});
{data: {id: {value: null}}},
{data: {id: {value: null}}},
{data: {id: {value: null}}}
]});
ok(are(listview.$element.find('tbody th'),
'.oe-record-selector'));
ok(are(listview.$element.find('tbody th input'),
':checkbox:not([name])'));
});
asyncTest('render no checkbox if selectable=false', 1, function () {
test('render no checkbox if selectable=false', 1, function () {
var listview = new openerp.base.ListView(
{}, null,
fuck_that_shit, null,
'qunit-fixture', {model: null, ids: [null, null, null], index: 0}, false,
{selectable: false});
@@ -62,33 +70,32 @@ $(document).ready(function () {
{data: {id: {value: null}}},
{data: {id: {value: null}}},
{data: {id: {value: null}}}
]}).then(function () {
equal(listview.$element.find('tbody th').length, 0);
start();
});
]});
equal(listview.$element.find('tbody th').length, 0);
});
asyncTest('select a bunch of records', 2, function () {
test('select a bunch of records', 2, function () {
var listview = new openerp.base.ListView(
{}, null, 'qunit-fixture', {model: null, ids: [1, 2, 3], index: 0});
fuck_that_shit, null, 'qunit-fixture',
{model: null, ids: [1, 2, 3], index: 0});
listview.on_loaded(fvg);
listview.do_fill_table({records: [
{data: {id: {value: 1}}},
{data: {id: {value: 2}}},
{data: {id: {value: 3}}}
]}).then(function () {
listview.$element.find('tbody th input:eq(2)')
.attr('checked', true);
deepEqual(listview.get_selection(), [3]);
listview.$element.find('tbody th input:eq(1)')
.attr('checked', true);
deepEqual(listview.get_selection(), [2, 3]);
start();
});
]});
// TODO: find less intrusive way to get selection count of list view?
listview.$element.find('tbody th input:eq(2)')
.attr('checked', true);
deepEqual(listview.list.get_selection(), [3]);
listview.$element.find('tbody th input:eq(1)')
.attr('checked', true);
deepEqual(listview.list.get_selection(), [2, 3]);
});
asyncTest('render deletion button if list is deletable', 1, function () {
test('render deletion button if list is deletable', 1, function () {
var listview = new openerp.base.ListView(
{}, null, 'qunit-fixture', {model: null, ids: [null, null, null], index: 0});
fuck_that_shit, null, 'qunit-fixture',
{model: null, ids: [null, null, null], index: 0});
listview.on_loaded(fvg);
@@ -96,18 +103,17 @@ $(document).ready(function () {
{data: {id: {value: null}}},
{data: {id: {value: null}}},
{data: {id: {value: null}}}
]}).then(function () {
equal(
listview.$element.find('tbody tr td.oe-record-delete button').length,
3);
start();
});
]});
equal(
listview.$element.find('tbody tr td.oe-record-delete button').length,
3);
});
asyncTest('deletion button should lead on deletion in the dataset',
test('deletion button should lead on deletion in the dataset',
2, function () {
var deleted;
var listview = new openerp.base.ListView(
{}, null, 'qunit-fixture', {model: null, unlink: function (ids) {
fuck_that_shit, null, 'qunit-fixture',
{model: null, unlink: function (ids) {
deleted = ids;
}, ids: [1, 2, 3], index: 0});
@@ -117,18 +123,17 @@ $(document).ready(function () {
{data: {id: {value: 1}}},
{data: {id: {value: 2}}},
{data: {id: {value: 3}}}
]}).then(function () {
listview.$element.find('tbody td.oe-record-delete:eq(2) button').click();
deepEqual(deleted, [3]);
listview.$element.find('tbody td.oe-record-delete:eq(0) button').click();
deepEqual(deleted, [1]);
start();
});
]});
listview.$element.find('tbody td.oe-record-delete:eq(2) button').click();
deepEqual(deleted, [3]);
listview.$element.find('tbody td.oe-record-delete:eq(0) button').click();
deepEqual(deleted, [1]);
});
asyncTest('multiple records deletion', 1, function () {
test('multiple records deletion', 1, function () {
var deleted;
var listview = new openerp.base.ListView(
{}, null, 'qunit-fixture', {model: null, unlink: function (ids) {
fuck_that_shit, null, 'qunit-fixture',
{model: null, unlink: function (ids) {
deleted = ids;
}, ids: [1, 2, 3], index: 0});
@@ -138,15 +143,13 @@ $(document).ready(function () {
{data: {id: {value: 1}}},
{data: {id: {value: 2}}},
{data: {id: {value: 3}}}
]}).then(function () {
listview.$element.find('tbody th input:eq(2)')
.attr('checked', true);
listview.$element.find('tbody th input:eq(1)')
.attr('checked', true);
]});
listview.$element.find('tbody th input:eq(2)')
.attr('checked', true);
listview.$element.find('tbody th input:eq(1)')
.attr('checked', true);
listview.$element.find('#oe-list-delete').click();
deepEqual(deleted, [2, 3]);
start();
});
listview.$element.find('#oe-list-delete').click();
deepEqual(deleted, [2, 3]);
});
});
Executable → Regular
+80 -164
View File
@@ -1,173 +1,89 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# setup from TinERP
# taken from straw http://www.nongnu.org/straw/index.html
# taken from gnomolicious http://www.nongnu.org/gnomolicious/
# adapted by Nicolas Évrard <nicoe@altern.org>
#
# doc/migrate is not included since about 6.1-dev
# doc/tests is not included
# python25-compat/*py should be in the openerp (and imported appropriately)
import sys
import os
from os.path import join, isfile
import glob
import re
import sys
from setuptools import setup
from setuptools import setup, find_packages
name = 'openerp-web-proto'
version = '6.0.1'
description = "Web Client of OpenERP, the Enterprise Management Software"
long_description = "OpenERP Web is the web client of the OpenERP, a free enterprise management software"
author = "OpenERP S.A."
author_email = "info@openerp.com"
support_email = 'support@openerp.com'
url = "http://www.openerp.com/"
download_url = ''
license = "OEPL"
# Backports os.walk with followlinks from python 2.6.
# Needed to add all addons files to data_files for Windows packaging.
def walk_followlinks(top, topdown=True, onerror=None, followlinks=False):
from os.path import join, isdir, islink
from os import listdir, error
try:
names = listdir(top)
except error, err:
if onerror is not None:
onerror(err)
return
dirs, nondirs = [], []
for name in names:
if isdir(join(top, name)):
dirs.append(name)
else:
nondirs.append(name)
if topdown:
yield top, dirs, nondirs
for name in dirs:
path = join(top, name)
if followlinks or not islink(path):
for x in walk_followlinks(path, topdown, onerror, followlinks):
yield x
if not topdown:
yield top, dirs, nondirs
if sys.version_info < (2, 6):
os.walk = walk_followlinks
py2exe_keywords = {}
py2exe_data_files = []
if os.name == 'nt':
version_dash_incompatible = False
if 'bdist_rpm' in sys.argv:
version_dash_incompatible = True
try:
import py2exe
py2exe_keywords['console'] = [
{ "script": "openerp-server",
"icon_resources": [(1, join("pixmaps","openerp-icon.ico"))],
}]
py2exe_keywords['options'] = {
"py2exe": {
"skip_archive": 1,
"optimize": 2,
"dist_dir": 'dist',
"packages": [
"lxml", "lxml.builder", "lxml._elementpath", "lxml.etree",
"lxml.objectify", "decimal", "xml", "xml", "xml.dom", "xml.xpath",
"encodings", "dateutil", "pychart", "PIL", "pyparsing",
"pydot", "asyncore","asynchat", "reportlab", "vobject",
"HTMLParser", "select", "mako", "poplib",
"imaplib", "smtplib", "email", "yaml", "DAV",
"uuid", "commands", "openerp",
],
"excludes" : ["Tkconstants","Tkinter","tcl"],
}
}
# TODO is it still necessary now that we don't use the library.zip file?
def data_files():
'''For Windows, we consider all the addons as data files.
It seems also that package_data below isn't honored by py2exe.'''
files = []
os.chdir('openerp')
for (dp, dn, names) in os.walk('addons'):
files.append((join('openerp',dp), map(lambda x: join('openerp', dp, x), names)))
os.chdir('..')
files.append(('openerp', [join('openerp', 'import_xml.rng'),]))
from py2exe_utils import opts
version_dash_incompatible = True
except ImportError:
opts = {}
if version_dash_incompatible:
version = version.split('-')[0]
# copy pytz/timzeone
# TODO check if we have to also copy dateutil's timezone data.
import pytz
# Make sure the layout of pytz hasn't changed
assert (pytz.__file__.endswith('__init__.pyc') or
pytz.__file__.endswith('__init__.py')), pytz.__file__
pytz_dir = os.path.dirname(pytz.__file__)
FILE_PATTERNS = \
r'.+\.(py|cfg|po|pot|mo|txt|rst|gif|png|jpg|ico|mako|html|js|css|htc|swf)$'
def find_data_files(source, patterns=FILE_PATTERNS):
file_matcher = re.compile(patterns, re.I)
out = []
for base, _, files in os.walk(source):
cur_files = []
for f in files:
if file_matcher.match(f):
cur_files.append(os.path.join(base, f))
if cur_files:
out.append(
(base, cur_files))
saved_dir = os.getcwd()
os.chdir(pytz_dir)
for dp, dn, names in os.walk('zoneinfo'):
files.append((join('pytz',dp), map(lambda x: join(pytz_dir, dp, x), names)))
os.chdir(saved_dir)
return out
return files
py2exe_data_files = data_files()
execfile(join('openerp', 'release.py'))
setup(name = name,
version = version,
description = description,
long_description = long_desc,
url = url,
author = author,
author_email = author_email,
classifiers = filter(None, classifiers.split("\n")),
license = license,
data_files = [
(join('man', 'man1'), ['man/openerp-server.1']),
(join('man', 'man5'), ['man/openerp_serverrc.5']),
('doc', filter(isfile, glob.glob('doc/*'))),
] + py2exe_data_files,
scripts = ['openerp-server'],
packages = find_packages(),
include_package_data = True,
package_data = {
'': ['*.yml', '*.xml', '*.po', '*.pot', '*.csv'],
},
dependency_links = ['http://download.gna.org/pychart/'],
install_requires = [
# We require the same version as caldav for lxml.
'lxml==2.1.5',
'mako',
'python-dateutil',
'psycopg2',
# TODO the pychart package we include in openerp corresponds to PyChart 1.37.
# It seems there is a single difference, which is a spurious print in generate_docs.py.
# It is probably safe to move to PyChart 1.39 (the latest one).
# (Let setup.py choose the latest one, and we should check we can remove pychart from
# our tree.)
'pychart',
'pydot',
'pytz',
'reportlab',
'caldav',
'pyyaml',
'pywebdav',
'feedparser',
],
extras_require = {
'SSL' : ['pyopenssl'],
},
**py2exe_keywords
setup(
name=name,
version=version,
description=description,
long_description=long_description,
author=author,
author_email=author_email,
url=url,
download_url=download_url,
license=license,
install_requires=[
"CherryPy >= 3.1.2",
"Babel >= 0.9.4",
"simplejson >= 2.0.9",
"python-dateutil >= 1.4.1",
"pytz",
],
tests_require=[
'unittest2',
'mock',
],
test_suite = 'unittest2.collector',
zip_safe=False,
packages=[
'addons',
'addons.base',
'addons.base.controllers',
'addons.base_calendar',
'addons.base_hello',
'openerpweb',
],
classifiers=[
'Development Status :: 6 - Production/Stable',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Environment :: Web Environment',
'Topic :: Office/Business :: Financial',
],
scripts=['scripts/openerp-web'],
data_files=(find_data_files('addons')
+ opts.pop('data_files', [])
),
**opts
)