bzr revid: nicolas.vanhoren@openerp.com-20110519115807-9wliqklt0qme0ern
This commit is contained in:
niv-openerp
2011-05-19 13:58:07 +02:00
30 changed files with 1205 additions and 1832 deletions
+44 -19
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import base64
import glob, os
import pprint
from xml.etree import ElementTree
@@ -361,24 +362,6 @@ class DataSet(openerpweb.Controller):
@openerpweb.jsonrequest
def read(self, request, model, ids, fields=False):
return self.do_search_read(request, model, ids, fields)
def search_read(self, request, model, ids, fields=False):
""" Performs a read()
:param request: a JSON-RPC request object
:type request: openerpweb.JsonRequest
:param str model: the name of the model to search on
:param ids: the ids of the records
:type ids: [?]
:param fields: a list of the fields to return in the result records
:type fields: [str]
:returns: a list of result records
:rtype: list
"""
Model = request.session.model(model)
reads = Model.read(ids, fields or False, request.context)
reads.sort(key=lambda obj: ids.index(obj['id']))
return reads
@openerpweb.jsonrequest
def get(self, request, model, ids, fields=False):
@@ -403,7 +386,7 @@ class DataSet(openerpweb.Controller):
:rtype: list
"""
Model = request.session.model(model)
records = Model.read(ids, fields)
records = Model.read(ids, fields, request.context)
record_map = dict((record['id'], record) for record in records)
@@ -572,6 +555,48 @@ class FormView(View):
fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar)
return {'fields_view': fields_view}
@openerpweb.httprequest
def image(self, request, session_id, model, id, field, **kw):
cherrypy.response.headers['Content-Type'] = 'image/png'
Model = request.session.model(model)
try:
if not id:
res = Model.default_get([field], request.context).get(field, '')
else:
res = Model.read([id], [field], request.context)[0].get(field, '')
return base64.decodestring(res)
except:
return self.placeholder()
def placeholder(self):
return open(os.path.join(openerpweb.path_addons, 'base', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
@openerpweb.httprequest
def upload(self, request, session_id, callback, ufile=None):
cherrypy.response.timeout = 500
headers = {}
for key, val in cherrypy.request.headers.iteritems():
headers[key.lower()] = val
size = int(headers.get('content-length', 0))
# TODO: might be usefull to have a configuration flag for max-lenght file uploads
try:
out = """<script language="javascript" type="text/javascript">
var win = window.top.window,
callback = win[%s];
if (typeof(callback) === 'function') {
callback.apply(this, %s);
} else {
win.jQuery('#oe_notification', win.document).notify('create', {
title: "Ajax File Upload",
text: "Could not find callback"
});
}
</script>"""
data = ufile.file.read()
args = [size, ufile.filename, ufile.headers.getheader('Content-Type'), base64.encodestring(data)]
except Exception as e:
args = [False, e.message]
return out % (simplejson.dumps(callback), simplejson.dumps(args))
class ListView(View):
_cp_path = "/base/listview"
+480
View File
@@ -0,0 +1,480 @@
/*
http://www.JSON.org/json2.js
2011-02-23
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, strict: false, regexp: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
var JSON;
if (!JSON) {
JSON = {};
}
(function () {
"use strict";
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' : gap ?
'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' : gap ?
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
'{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());
+10 -7
View File
@@ -1,4 +1,5 @@
// TODO: trim support
// TODO: line number -> https://bugzilla.mozilla.org/show_bug.cgi?id=618650
var QWeb2 = {
expressions_cache: {},
reserved_words: 'true,false,NaN,null,undefined,debugger,in,instanceof,new,function,return,this,typeof,eval,Math,RegExp,Array,Object'.split(','),
@@ -295,8 +296,7 @@ QWeb2.Engine = (function() {
compile : function(node) {
var e = new QWeb2.Element(this, node);
var template = node.getAttribute(this.prefix + '-name');
return "(function(dict) {\n" +
" /* 'this' refers to Qweb2.Engine instance */\n" +
return " /* 'this' refers to Qweb2.Engine instance */\n" +
" var context = { engine : this, template : " + (this.tools.js_escape(template)) + " };\n" +
" dict = dict || {};\n" +
" dict['__template__'] = '" + template + "';\n" +
@@ -306,8 +306,7 @@ QWeb2.Engine = (function() {
" /* END OF TEMPLATE */ } catch(error) {\n" +
" context.engine.tools.exception('Runtime Error: ' + error, context);\n" +
" }\n" +
" return r.join('');\n" +
"});";
" return r.join('');";
},
render : function(template, dict) {
if (this.debug && window['console'] !== undefined) {
@@ -332,12 +331,16 @@ QWeb2.Engine = (function() {
}
var code = this.compile(this.templates[template]), tcompiled;
try {
// tcompiled = new Function(code); TODO: Check if could use this in order to avoid eval
tcompiled = eval(code);
tcompiled = new Function(['dict'], code);
} catch (error) {
console.log(code);
if (this.debug && window.console) {
console.log(code);
}
this.tools.exception("Error evaluating template: " + error, { template: name });
}
if (!tcompiled) {
this.tools.exception("Error evaluating template: (IE?)" + error, { template: name });
}
this.compiled_templates[template] = tcompiled;
return this.render(template, dict);
} else {
+4
View File
@@ -20,6 +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/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>
@@ -35,6 +36,9 @@
<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() {
+4
View File
@@ -0,0 +1,4 @@
.openerp .menu span {
min-width: 0px;
}
+34 -29
View File
@@ -95,57 +95,40 @@ body.openerp {
.openerp .menu {
height: 34px;
position: relative;
white-space: nowrap;
background: url(../img/menu-bg.png) repeat-x;
}
.openerp .menu ul {
margin: 0;
padding: 0;
list-style: none;
overflow: hidden;
position: absolute;
left: 0;
right: 0;
text-align: center;
}
.openerp .menu li {
display: -moz-inline-stack;
display: inline; /*IE7 */
display: inline-block;
margin: 6px 1px;
text-align: center;
text-transform: uppercase;
}
.openerp .menu a {
display: block;
float: left;
padding: 0 0 0 3px;
background: url(../img/menu-item.png) no-repeat;
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;
float: left;
min-width: 60px;
height: 23px;
padding: 0 9px 0 6px;
background: url(../img/menu-item.png) 100% 0 no-repeat;
line-height: 22px;
line-height: 23px;
font-weight: bold;
font-size: 85%;
}
.openerp .menu ul li a:hover,
.openerp .menu li a:focus,
.openerp .menu ul li a.active {
.openerp .menu a:hover,
.openerp .menu a:focus,
.openerp .menu a.active {
background-position: 0 -23px;
color: #fff;
}
.openerp .menu ul li a:hover span,
.openerp .menu ul li a:focus span,
.openerp .menu ul li a.active span {
.openerp .menu a:hover span,
.openerp .menu a:focus span,
.openerp .menu a.active span {
background-position: 100% -23px;
}
/* Secondary Menu */
@@ -156,6 +139,7 @@ body.openerp {
border-bottom: 1px solid #5A5858;
background: #5A5858;
vertical-align: top;
height: 100%;
}
.openerp .secondary_menu .menu_content {
padding: 0;
@@ -637,6 +621,27 @@ body.openerp {
-moz-background-origin: content-box;
-webkit-background-origin: content-box;
}
/* 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 {
overflow: hidden;
position: relative;
}
.openerp input.oe-binary-file {
z-index: 2;
line-height: 0;
font-size: 50px;
position: absolute;
/* Should be adjusted for all browsers */
top: -2px;
left: -700px;
opacity: 0;
filter: alpha(opacity = 0);
-ms-filter: "alpha(opacity=0)";
margin: 0;
padding:0;
}
/* Widgets */
.openerp .separator {
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

+5 -2
View File
@@ -44,8 +44,11 @@
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
if ( !initializing && this.init ) {
var ret = this.init.apply(this, arguments);
if (ret) { return ret; }
}
return this;
}
// Populate our constructed prototype object
+3
View File
@@ -296,6 +296,9 @@ openerp.base.Session = openerp.base.BasicController.extend( /** @lends openerp.b
// Construct a JSON-RPC2 request, method is currently unused
params.session_id = this.session_id;
params.context = typeof(params.context) != "undefined" ? params.context : this.context;
if (!params.context.bin_size) {
params.context.bin_size = true;
}
// Use a default error handler unless defined
error_callback = typeof(error_callback) != "undefined" ? error_callback : this.on_rpc_error;
+1 -2
View File
@@ -242,7 +242,6 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base.
ids: ids,
args: args
}, callback);
},
name_search: function (search_str, callback) {
search_str = search_str || '';
@@ -352,7 +351,7 @@ openerp.base.DataSetMany2Many = openerp.base.DataSetStatic.extend({
unlink: function(ids) {
// just do nothing
},
}
});
};
+59 -8
View File
@@ -71,6 +71,9 @@ 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);
}
},
do_hide: function () {
this.$element.hide();
@@ -321,7 +324,7 @@ openerp.base.form.compute_domain = function(expr, fields) {
var ex = expr[i];
if (ex.length == 1) {
var top = stack.pop();
switch (ex[0]) {
switch (ex) {
case '|':
stack.push(stack.pop() || top);
continue;
@@ -332,7 +335,7 @@ openerp.base.form.compute_domain = function(expr, fields) {
stack.push(!top);
continue;
default:
throw new Error('Unknown domain operator ' + ex[0]);
throw new Error('Unknown domain operator ' + ex);
}
}
@@ -362,17 +365,17 @@ openerp.base.form.compute_domain = function(expr, fields) {
stack.push(field >= val);
break;
case 'in':
stack.push(_.indexOf(val, field) > -1);
stack.push(_(val).contains(field));
break;
case 'not in':
stack.push(_.indexOf(val, field) == -1);
stack.push(!_(val).contains(field));
break;
default:
this.log("Unsupported operator in attrs :", op);
}
}
return _.indexOf(stack, false) == -1;
}
return _.all(stack);
};
openerp.base.form.Widget = openerp.base.Controller.extend({
init: function(view, node) {
@@ -891,7 +894,7 @@ openerp.base.form.FieldMany2OneDatasSet = openerp.base.DataSetStatic.extend({
},
write: function (id, data, callback) {
this._super(id, data, callback);
},
}
});
openerp.base.form.FieldMany2OneViewManager = openerp.base.ViewManager.extend({
@@ -1188,10 +1191,58 @@ openerp.base.form.FieldImage = openerp.base.form.Field.extend({
init: function(view, node) {
this._super(view, node);
this.template = "FieldImage";
this.iframe = this.element_id + '_iframe';
},
start: function() {
this._super.apply(this, arguments);
this.$element.find('input.oe-binary-file').change(this.on_file_change);
this.$element.find('button.oe-binary-file-clear').click(this.on_clear);
},
set_value_from_ui: function() {
},
set_image_maxwidth: function() {
this.$element.find('img.oe-binary-image').css('max-width', this.$element.width());
},
on_file_change: function() {
this.set_image_maxwidth();
// 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();
},
toggle_progress: function() {
this.$element.find('div.oe-binary-progress, div.oe-binary').toggle();
},
on_file_uploaded: function(size, name, content_type, img) {
delete(window[this.iframe]);
if (size === false) {
this.notification.warn("File Upload", "There was a problem while uploading your file");
// TODO: use openerp web exception handler
console.log("Error while uploading file : ", name);
} else {
this.value = img;
this.$element.find('img.oe-binary-image').attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + img);
this.on_ui_change();
}
this.toggle_progress();
},
on_clear: function() {
if (this.value !== false) {
this.value = false;
this.$element.find('img.oe-binary-image').attr('src', '/base/static/src/img/placeholder.png');
this.on_ui_change();
}
return false;
},
set_value: function(value) {
this._super.apply(this, arguments);
this.$element.find('img').show().attr('src', 'data:image/png;base64,' + this.value);
this.set_image_maxwidth();
var url = '/base/formview/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.$element.find('img.oe-binary-image').attr('src', url);
}
});
+3
View File
@@ -251,6 +251,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
this.do_reload();
this.hidden = false;
}
if (this.view_manager && this.view_manager.sidebar) {
this.view_manager.sidebar.refresh(true);
}
},
do_hide: function () {
this.$element.hide();
+2 -2
View File
@@ -90,7 +90,7 @@ openerp.base.m2o = function(openerp){
return true;
}
ui.item.value = self.element.data("autocomplete").term.length ? self.element.val() + '[' + ui.item.orig_val.substring(self.element.data("autocomplete").term.length) + ']' : this.lastSearch;
},
}
});
@@ -111,4 +111,4 @@ openerp.base.m2o = function(openerp){
});
}
});
}
}
+36 -8
View File
@@ -24,7 +24,8 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
sidebar : true,
search_view : true,
new_window : false,
toolbar : true,
views_switcher : true,
action_buttons : true,
pager : true
}, action.flags || {});
// instantiate the right controllers by understanding the action
@@ -46,6 +47,7 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
viewmanager.start();
this.dialog_stack.push(viewmanager);
} 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 });
@@ -79,6 +81,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
this.active_view = null;
this.views_src = views;
this.views = {};
this.flags = this.flags || {};
},
/**
* @returns {jQuery.Deferred} initial view loading promise
@@ -93,6 +96,9 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
_.each(this.views_src, function(view) {
self.views[view[1]] = { view_id: view[0], controller: null };
});
if (this.flags.views_switcher === false) {
this.$element.find('.oe_vm_switch').hide();
}
// switch to the first one in sequence
return this.on_mode_switch(this.views_src[0][1]);
},
@@ -116,7 +122,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
this.views[view_type].controller = controller;
}
if(this.searchview) {
if (this.searchview) {
if (view.controller.searchable === false) {
this.searchview.hide();
} else {
@@ -152,6 +158,9 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
this.searchview.stop();
}
this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search", this.dataset, view_id, search_defaults);
if (this.flags.search_view === false) {
this.searchview.hide();
}
this.searchview.on_search.add(function(domains, contexts, groupbys) {
self.views[self.active_view].controller.do_search.call(
self, domains.concat(self.domains()),
@@ -201,7 +210,12 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({
}
this._super(session, element_id, dataset, action.views);
this.action = action;
if (action.flags.sidebar) {
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;
}
if (this.flags.sidebar) {
this.sidebar = new openerp.base.Sidebar(null, this);
}
},
@@ -281,17 +295,31 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
var section = {elements:toolbar[type[0]], label:type[1]};
self.sections.push(section);
});
this.refresh();
this.refresh(true);
},
refresh: function() {
this.$element.html(QWeb.render("ViewManager.sidebar.internal", _.extend({_:_}, this)));
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
&& (!new_view || view != 'list');
if (!the_condition) {
this.$element.addClass('closed-sidebar');
this.$element.removeClass('open-sidebar');
} else {
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").click(function(e) {
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");
@@ -306,7 +334,7 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({
},
start: function() {
this._super();
this.refresh();
this.refresh(false);
}
});
+98 -61
View File
@@ -28,8 +28,10 @@
<tr>
<td valign="top" id="oe_secondary_menu" class="secondary_menu">
</td>
<td valign="top" width="100%" height="100%">
<div id="oe_app" class="oe-application"></div>
<td valign="top" >
<div id="oe_app" class="oe-application">
<div style="width:100%;">&amp;nbsp;</div>
</div>
</td>
</tr>
<tr>
@@ -85,13 +87,17 @@
</div>
</t>
<t t-name="Menu">
<ul>
<li t-foreach="data.children" t-as="menu">
<a href="#" t-att-data-menu="menu.id">
<span><t t-esc="menu.name"/></span>
</a>
</li>
</ul>
<table align="center">
<tr>
<t t-foreach="data.children" t-as="menu">
<td>
<a href="#" t-att-data-menu="menu.id">
<span><t t-esc="menu.name"/></span>
</a>
</td>
</t>
</tr>
</table>
</t>
<t t-name="Menu.secondary">
<div style="display: none" class="menu_accordion" t-att-data-menu-parent="menu.id">
@@ -130,7 +136,7 @@
<td class="view-manager-main-content">
<!-- TODO prefix id with the element_id of the controller t-attf-id="#{prefix}_localid" -->
<div class="oe_vm_switch">
<t t-foreach="views" t-as="view">
<t t-if="views.length != 1" t-foreach="views" t-as="view">
<button type="button" t-att-data-view-type="view[1]">
<t t-esc="view[1]"/>
</button>
@@ -153,27 +159,31 @@
<tr t-if="options.selectable">
<th t-if="actions_span" t-att-colspan="actions_span"
class="oe-actions">
<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.deletable">
Delete
</button>
<t t-if="view_manager.action.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.deletable">
Delete
</button>
</t>
</th>
<th t-att-colspan="columns_count - actions_span"
class="oe-list-pager">
<button type="button" data-pager-action="first">First</button>
<button type="button" data-pager-action="previous"
>&lt;&lt;</button>
<t t-if="view_manager.action.flags.pager !== false">
<button type="button" data-pager-action="first">First</button>
<button type="button" data-pager-action="previous"
>&lt;&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-first">1</span>
to <span class="oe-pager-last">1</span>
of <span class="oe-pager-total">1</span>
<button type="button" data-pager-action="next">&gt;&gt;</button>
<button type="button" data-pager-action="last">Last</button>
<button type="button" data-pager-action="next">&gt;&gt;</button>
<button type="button" data-pager-action="last">Last</button>
</t>
</th>
</tr>
<tr t-if="options.header">
@@ -229,13 +239,13 @@
<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">
<div class="oe_form_buttons" t-if="view.view_manager.action.flags.action_buttons !== false">
<!--<button type="button" class="oe_form_button_save">Save</button>-->
<button type="button" class="oe_form_button_save_edit">Save &amp; Edit</button>
<!--<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">
<div class="oe_form_pager" t-if="view.view_manager.action.flags.pager !== false">
<button type="button" data-pager-action="first">First</button>
<button type="button" data-pager-action="previous">&lt;&lt;</button>
@@ -311,7 +321,7 @@
<t t-call="FieldChar"/>
</td>
<td width="16">
<button class="button" title="Send an e-mail with your default e-mail client">
<button type="button" class="button" title="Send an e-mail with your default e-mail client">
<img src="/base/static/src/img/icons/terp-mail-message-new.png"/>
</button>
</td>
@@ -377,13 +387,46 @@
</div>
</t>
<t t-name="FieldImage">
<img align="left" border="1" style="display: none"
t-att-id="widget.element_id + '_field'"
t-att-name="widget.name"
t-att-class="'field_' + widget.type"
t-att-width="widget.node.attrs.img_width || widget.node.attrs.width"
t-att-height="widget.node.attrs.img_height || widget.node.attrs.height"
/>
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center">
<img src="/base/static/src/img/placeholder.png" class="oe-binary-image" border="1"
t-att-id="widget.element_id + '_field'"
t-att-name="widget.name"
t-att-class="'field_' + widget.type"
t-att-width="widget.node.attrs.img_width || widget.node.attrs.width"
t-att-height="widget.node.attrs.img_height || widget.node.attrs.height"
/>
</td>
</tr>
<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>
</div>
<div class="oe-binary-progress" style="display: none">
<img src="/base/static/src/img/throbber.gif" width="16" height="16"/>
<b>Uploading ...</b>
</div>
<iframe t-att-id="widget.iframe" t-att-name="widget.iframe" style="display: none"> </iframe>
</td>
</tr>
</table>
</t>
<t t-name="WidgetButton">
<button type="button"
@@ -398,7 +441,7 @@
<h2 class="oe_view_title"><t t-esc="view.attrs['string']"/></h2>
<form>
<t t-call="SearchView.render_lines"/>
<div class="oe_search-view-buttons" style="text-align:right;">
<div class="oe_search-view-buttons" style="text-align: right;">
<input type="submit" value="Search"/>
<input type="reset" value="Clear"/>
</div>
@@ -547,37 +590,31 @@
<input t-att-id="element_id"/>
</t>
<t t-name="ViewManager.sidebar">
<div t-att-id="element_id" class="sidebar-main-div open-sidebar">
<div t-att-id="element_id" class="sidebar-main-div closed-sidebar">
</div>
</t>
<t t-name="ViewManager.sidebar.internal">
<t t-js="d">
d.the_condition = d.sections.length &gt; 0 &amp;&amp; d._.detect(d.sections, function(x) {
return x.elements.length &gt; 0;
}) != undefined
</t>
<t t-if="the_condition">
<a class="toggle-sidebar"></a>
<div t-att-id="element_id" class="sidebar-sub-div">
<div class="sidebar-displaying-div">
<t t-set="i" t-value="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 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>
<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>
<t t-set="i" t-value="i+1"/>
</ul>
</t>
</div>
<t t-set="i" t-value="i+1"/>
</t>
</div>
</t>
</div>
</t>
<t t-name="DialogWarning">
<div id="dialog-message" t-att-title="title">
+1 -2
View File
@@ -3,9 +3,8 @@
"version": "2.0",
"depends": ['base'],
"js": [
'static/lib/jquery.dashboard/js/jquery.dashboard.js',
'static/src/js/dashboard.js'
],
"css": ['static/lib/jquery.dashboard/css/dashboardui.css'],
"css": ['static/src/css/dashboard.css'],
'active': True
}
-1
View File
@@ -1 +0,0 @@
content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4
@@ -1,261 +0,0 @@
.ui-icon {
cursor:default;
}
.widgetheader {
cursor:move;
}
#layout-dialog .selected {
background: no-repeat scroll 0px -51px transparent;
}
#layout-dialog .layoutchoice {
width: 82px;
height: 51px;
float:left;
list-style-type:none;
margin:5px;
padding:0;
}
.selectedcolumn {
border: 3px dashed #aaaaaa;
}
.emptycolumn {
font-size:20px;
font-weight:bold;
color: #aaaaaa;
padding: 5px 5px 5px 5px;
}
.dashboard {
margin-top: 5px;
}
.right {
float:right;
}
.widgetheader {
padding:2px 2px 5px 5px;
}
.ui-icon {
float:left;
}
.widgetcontent {
padding:2px 2px 5px 5px;
}
.widget {
margin-bottom:10px;
}
.column {
float:left;
margin:0 1% -1.5em;
padding:0;
width:47.5%;
}
.layout-a .column {
width:98%;
}
.layout-a .column.second, .layout-a .column.third {
display:none;
}
.layout-aa .column {
width:47.5%;
}
.layout-aa .third {
display:none;
}
.layout-ba .column {
width:68%;
}
.layout-ba .first {
width:27%;
}
.layout-ba .third {
display:none;
}
.layout-ab .column {
width:27%;
}
.layout-ab .first {
width:68%;
}
.layout-ab .third {
display:none;
}
.layout-aaa .column {
width:30.9%;
}
/*body {
margin:0;
color:#333333;
font:12px/1.4 arial,FreeSans,Helvetica,sans-serif;
}*/
.headerlink {
color:#ffffff;
}
.headerlinks {
text-align:right;
margin-right:20px;
line-height:24px;
text-decoration:underline;
font-weight:bold;
}
.headerbox {
height:148px;
}
.ui-widget-overlay {
opacity:0.5;
}
/*.loading {
padding: 50px;
text-align: center;
background-image:loading.gif;
}*/
#layout-dialog ul {
margin:0;
padding:0;
}
#layout-dialog ul li a, #layout-dialog ul li a:link, #layout-dialog ul li a:visited {
border:1px solid #BBBBBB;
display:block;
float:left;
margin:0 1em 1em 0;
outline:medium none;
padding:0.35em;
width:auto;
}
.hidden {
display:none;
}
.controls {
border:1px solid #BBBBBB;
float:none;
margin:0;
padding:4px 0;
}
.controls {
width:100px;
background:none repeat scroll 0 0 #dddddd;
border:1px solid #6A8EB3;
color:#000000;
margin-top:-1px;
padding:4px 0;
position:absolute;
right:0;
z-index:2003;
}
.controls li {
float:none;
margin:0;
padding:0;
list-style-type:none;
margin:0 0 0 0.2em;
width:auto;
position:static;
}
.controls li a {
color:#000000;
font-weight:normal;
float:none;
margin:0;
text-decoration:none;
width:auto;
padding-left:5px;
}
.hiddenmenu {
position:relative;
}
.dialog .categories {
list-style:none outside none;
height: 414px;
}
.dialog .categories li.selected button {
color:#FFFFFF;
font-weight:800;
}
.dialog .categories li button {
background:none repeat scroll 0 0 transparent;
border:medium none;
color:#666666;
font-family:"segoe ui",helvetica,arial,sans-serif;
font-size:0.8em;
padding:0.4em 1.2em;
text-align:left;
width:100%;
}
ul.categories button {
cursor:pointer;
}
.widgetitem {
border:2px none white;
float:left;
font-size:0.77em;
height:142px;
margin:0;
overflow:hidden;
padding:0 20px 0 142px;
width:152px;
}
ol.widgets {
float:left;
list-style:none outside none;
margin:-10px;
padding:0;
width:auto;
}
.dialog .panel-body {
overflow:auto;
padding:10px;
}
.dialog .categories {
background:none repeat scroll 0 0 #FFFFFF;
border-right:1px solid #F0F0F0;
float:left;
height:100%;
list-style:none outside none;
margin:0 1.17em 0 0;
padding:10px 0 0;
width:25%;
}
.dialog .categories li.selected {
background:none repeat scroll 0 0 #6699CC;
color:#FFFFFF;
}
.widgetitem .add-button {
float:left;
margin:81px 0 0 -131px;
width:auto;
}
.widgetitem h3 {
margin:11px 0 0;
padding:0;
}
.widgetitem img {
border:1px solid #999999;
float:left;
margin:10px 0 0 -132px;
}
@@ -1,936 +0,0 @@
/*
* dashboard 1.0
* http://www.gxdeveloperweb.com/dashboard/
*
* Copyright (c) 2010 Mark Machielsen
*
* Dual licensed under the MIT and GPL licenses (same as jQuery):
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
(function($) { // Create closure.
// Constructor for dashboard object.
$.fn.dashboard = function(options) {
// Public properties of dashboard.
var dashboard = {};
var loading;
var widgetDirectoryUrl;
dashboard.layout;
dashboard.element = this;
dashboard.id = this.attr("id");
dashboard.widgets = {};
dashboard.widgetsToAdd = {};
dashboard.widgetCategories = {};
dashboard.initialized = false;
// Public methods
dashboard.serialize = function() {
dashboard.log('entering serialize function',1);
var r = '{"layout": "' + dashboard.layout.id + '", "data" : [';
// add al widgets in the right order
var i=0;
if ($('.' + opts.columnClass).length == 0) dashboard.log(opts.columnClass + ' class not found',5);
$('.' + opts.columnClass).each(function() {
$(this).children().each(function() {
if ($(this).hasClass(opts.widgetClass)) {
if (i > 0) { r+= ','; }
r+= (dashboard.getWidget($(this).attr("id"))).serialize();
i++;
}
});
});
r+= ']}';
return r;
}
dashboard.log = function(msg, level) {
if (level >= opts.debuglevel && typeof console != 'undefined') {
var l = '';
if (level == 1) l = 'INFO';
if (level == 2) l = 'EVENT';
if (level == 3) l = 'WARNING';
if (level == 5) l = 'ERROR';
console.log(l + ' - ' + msg);
}
}
dashboard.setLayout = function(layout) {
if (layout != null) {
dashboard.log('entering setLayout function with layout ' + layout.id,1);
} else {
dashboard.log('entering setLayout function with layout null',1);
}
dashboard.layout = layout;
loading.remove();
if (dashboard.layout != null) {
if (typeof opts.layoutClass != 'undefined') {
this.element.find('.' + opts.layoutClass).addClass(dashboard.layout.classname);
} else {
this.element.html(dashboard.layout.html);
}
}
// make the columns sortable, see http://jqueryui.com/demos/sortable/ for explaination
$('.' + opts.columnClass).sortable({
connectWith: $('.' + opts.columnClass),
opacity: opts.opacity,
handle: '.' + opts.widgetHeaderClass,
over: function(event, ui) {
$(this).addClass("selectedcolumn");
},
out: function(event, ui) {
$(this).removeClass("selectedcolumn");
},
receive: function(event, ui) {
// update the column attribute for the widget
var w = dashboard.getWidget(ui.item.attr("id"));
w.column = getColumnIdentifier($(this).attr("class"));
dashboard.log('dashboardStateChange event thrown for widget ' + w.id,2);
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetMoved","widget":w});
dashboard.log('widgetDropped event thrown for widget ' + w.id,2);
w.element.trigger("widgetDropped",{"widget":w});
},
deactivate: function(event, ui) {
// This event is called for each column
dashboard.log('Widget is dropped: check if the column is now empty.',1);
var childLength = $(this).children().length;
if (childLength == 0) {
dashboard.log('adding the empty text to the column',1);
$(this).html('<div class="emptycolumn">' + opts.emptyColumnHtml + '</div>');
} else {
if (childLength == 2) {
// remove the empty column HTML
$(this).find('.emptycolumn').remove();
}
}
},
start: function(event, ui) {
ui.item.find('.' + opts.widgetTitleClass).addClass('noclick');
},
stop: function(event, ui) {
//sorting changed (within one list)
setTimeout(function(){
ui.item.find('.' + opts.widgetTitleClass).removeClass('noclick');
}, 300);
}
});
fixSortableColumns();
// trigger the dashboardLayoutLoaded event
dashboard.log('dashboardLayoutLoaded event thrown',2);
dashboard.element.trigger("dashboardLayoutLoaded");
}
// This is a workaround for the following problem: when I drag a widget from column2 to column1, sometimes the widget is
// moved to column3, which is not visible
function fixSortableColumns() {
dashboard.log('entering fixSortableColumns function',1);
$('.nonsortablecolumn').removeClass('nonsortablecolumn').addClass(opts.columnClass);
$('.' + opts.columnClass).filter(function() {return $(this).css("display") == 'none'}).addClass('nonsortablecolumn').removeClass(opts.columnClass);
}
function getColumnIdentifier(classes) {
dashboard.log('entering getColumnIdentifier function',1);
var r;
var s = classes.split(" ");
for (var i = 0;i < s.length;i++) {
if (s[i].indexOf(opts.columnPrefix) === 0) { r = s[i] };
};
return r.replace(opts.columnPrefix,'');
}
dashboard.loadLayout = function() {
dashboard.log('entering loadLayout function',1);
if (typeof opts.json_data.url != 'undefined' && opts.json_data.url.length > 0) {
// ajax option
dashboard.log('Getting JSON feed : ' + opts.json_data.url,1);
$.getJSON(opts.json_data.url, function(json) {
if (json == null) {
alert('Unable to get json. If you are using chrome: there is an issue with loading json with local files. It works on a server :-)',5);
return;
}
// set the layout
var currentLayout = (typeof dashboard.layout != 'undefined') ? dashboard.layout : getLayout(json.layout);
dashboard.setLayout(currentLayout);
dashboard.loadWidgets(json.data);
});
} else {
// set the layout
var currentLayout = (typeof dashboard.layout != 'undefined') ? dashboard.layout : getLayout(opts.json_data.layout);
dashboard.setLayout(currentLayout);
dashboard.loadWidgets(opts.json_data.data);
}
};
dashboard.addWidget = function(obj, column) {
dashboard.log('entering addWidget function',1);
// add the widget to the column
var wid = obj.id;
// check if the widget is already registered and available in the dom
if (typeof dashboard.widgets[wid] != 'undefined' && $('#' + wid).length > 0) {
var wi = $('#' + wid);
column = dashboard.widgets[wid].column;
// add it to the column
wi.appendTo(column);
} else {
// build the widget
dashboard.log('Applying template : ' + opts.widgetTemplate,1);
if ($('#' + opts.widgetTemplate).length == 0) dashboard.log('Template "' + opts.widgetTemplate + ' not found',5);
var widgetStr = tmpl($('#' + opts.widgetTemplate).html(), obj);
var wi = $(widgetStr);
// add it to the column
wi.appendTo(column);
if(column.find('.emptycolumn').length)
column.find('.emptycolumn').remove();
dashboard.widgets[wid] = widget({
id: wid,
element: wi,
column: obj.column,
url: (typeof obj.url != 'undefined' ? obj.url : null),
editurl: obj.editurl,
title: obj.title,
open: obj.open,
metadata: obj.metadata
});
}
dashboard.log('widgetAdded event thrown for widget ' + wid,2);
dashboard.widgets[wid].element.trigger("widgetAdded", {"widget":dashboard.widgets[wid]});
if (dashboard.initialized) {
dashboard.log('dashboardStateChange event thrown for widget ' + wid,2);
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetAdded","widget":wi});
}
}
dashboard.loadWidgets = function(data) {
dashboard.log('entering loadWidgets function',1);
dashboard.element.find('.' + opts.columnClass).empty();
// This is for the manual feed
$(data).each(function() {
var column = this.column;
dashboard.addWidget(this, dashboard.element.find('.' + opts.columnPrefix + column));
}); // end loop for widgets
// check if there are widgets in the temp dashboard which needs to be moved
// this is not the correct place, but otherwise we are too late
// check if there are still widgets in the temp
$('#tempdashboard').find('.' + opts.widgetClass).each(function() {
// append it to the first column
var firstCol = dashboard.element.find('.' + opts.columnClass + ':first');
$(this).appendTo(firstCol);
// set the new column
dashboard.getWidget($(this).attr("id")).column = firstCol.attr("id");
});
$('#tempdashboard').remove();
// add the text to the empty columns
$('.' + opts.columnClass).each(function() {
if ($(this).children().length == 0) {
$(this).html('<div class="emptycolumn">' + opts.emptyColumnHtml + '</div>');
}
});
dashboard.initialized = true;
};
dashboard.init = function() {
dashboard.log('entering init function',1);
// load the widgets as fast as we can. After that add the binding
dashboard.loadLayout();
}
dashboard.getWidget = function(id) {
dashboard.log('entering getWidget function',1);
var wi = dashboard.widgets[id];
if (typeof wi != 'undefined') {
return wi;
} else {
return null;
}
}
// Merge in the caller's options with the defaults.
var opts = $.extend({}, $.fn.dashboard.defaults, options);
var addOpts = $.extend({}, $.fn.dashboard.defaults.addWidgetSettings, options.addWidgetSettings);
var layoutOpts = $.extend({}, $.fn.dashboard.defaults.editLayoutSettings, options.editLayoutSettings);
// Execution 'forks' here and restarts in init(). Tell the user we're busy with a loading.
var loading = $(opts.loadingHtml).appendTo(dashboard.element);
/**
* widget object
* Private sub-class of dashboard
* Constructor starts
*/
function widget(widget) {
dashboard.log('entering widget constructor',1);
// Merge default options with the options defined for this widget.
widget = $.extend({}, $.fn.dashboard.widget.defaults, widget);
// public functions
widget.openContent = function() {
// hide the open link, show the close link
widget.element.find('.widgetOpen').hide();
widget.element.find('.widgetClose').show();
dashboard.log('entering openContent function',1);
widget.open = true;
if (!widget.loaded) {
// load the content in the widget if the state == open
if (this.url != '' && this.url != null && typeof this.url != 'undefined') {
// add the loading
$(opts.loadingHtml).appendTo(widget.element.find('.' + opts.widgetContentClass));
dashboard.log('widgetShow event thrown for widget ' + widget.id,2);
widget.element.trigger("widgetShow", {"widget":widget});
widget.element.find('.' + opts.widgetContentClass).load(this.url, function(response, status, xhr) {
if (status == "error") {
widget.element.find('.' + opts.widgetContentClass).html(opts.widgetNotFoundHtml);
}
widget.loaded = true;
dashboard.log('widgetLoaded event thrown for widget ' + widget.id,2);
widget.element.trigger("widgetLoaded", {"widget":widget});
});
} else {
dashboard.log('widgetShow event thrown for widget ' + widget.id,2);
widget.element.trigger("widgetShow", {"widget":widget});
dashboard.log('widgetLoaded event thrown',2);
widget.element.trigger("widgetLoaded", {"widget":widget});
}
} else {
dashboard.log('widgetShow event thrown for widget ' + widget.id,2);
widget.element.trigger("widgetShow", {"widget":widget});
}
if (dashboard.initialized) {
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetOpened","widget":widget});
}
};
widget.refreshContent = function() {
dashboard.log('entering refreshContent function',1);
widget.loaded = false;
if (widget.open) {
widget.openContent();
}
}
widget.setTitle = function(newTitle){
dashboard.log('entering setTitle function',1);
widget.title=newTitle;
widget.element.find('.' + opts.widgetTitleClass).html(newTitle);
if (dashboard.initialized) {
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
dashboard.element.trigger("dashboardStateChange",{"stateChange":"titleChanged","widget":widget});
}
}
widget.closeContent = function() {
dashboard.log('entering closeContent function',1);
widget.open = false;
dashboard.log('widgetHide event thrown for widget ' + widget.id,2);
widget.element.trigger("widgetHide", {"widget":widget});
// show the open link, hide the close link
widget.element.find('.widgetOpen').show();
widget.element.find('.widgetClose').hide();
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetClosed","widget":widget});
};
widget.addMetadataValue = function(name, value) {
dashboard.log('entering addMetadataValue function',1);
if (typeof widget.metadata == 'undefined') {
widget.metadata = {};
}
widget.metadata[name] = value;
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
dashboard.element.trigger("dashboardStateChange",{"stateChange":"metadataChanged","widget":widget});
};
widget.openMenu = function() {
dashboard.log('entering openMenu function',1);
widget.element.find('.' + opts.menuClass).show();
};
widget.closeMenu = function() {
dashboard.log('entering closeMenu function',1);
widget.element.find('.' + opts.menuClass).hide();
};
widget.remove = function() {
dashboard.log('entering remove function',1);
widget.element.remove();
dashboard.log('widgetDeleted event thrown for widget ' + widget.id,2);
widget.element.trigger('widgetDeleted', {"widget":widget});
dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2);
dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetRemoved","widget":widget});
};
widget.serialize = function() {
dashboard.log('entering serialize function',1);
var r = '{"title" : "' + widget.title + '", "id" : "' + widget.id + '", "column" : "' + widget.column + '","editurl" : "' + widget.editurl + '","open" : ' + widget.open + ',"url" : "' + widget.url + '"';
if (typeof widget.metadata != 'undefined') {
r+= ',"metadata":{'
var obj = widget.metadata;
var i=0;
for(var item in obj) {
if (i > 0) { r+= ',' };
// FIXME: support for more than string, eg numbers subobjects
r+= '"' + item + '":"' + obj[item] + '"';
i++;
}
r+= '}'
}
r += '}';
return r;
};
widget.openFullscreen = function() {
dashboard.log('entering openFullscreen function',1);
widget.fullscreen = true;
// create a clone
var clone = widget.element.clone();
// move the dashboard
var temp = $('<div style="display:none" id="tempdashboard_' + dashboard.id + '"></div>');
temp.appendTo($("body"));
dashboard.element.children().appendTo(temp);
// add the clone to the dashboard
var fs = $('<ul id="fullscreen_' + dashboard.id + '"></ul>');
fs.appendTo(dashboard.element);
clone.appendTo(fs);
};
widget.closeFullscreen = function() {
dashboard.log('entering closeFullscreen function',1);
widget.fullscreen = false;
// remove the fullscreen
$('#fullscreen_' + dashboard.id).remove();
// move the content from the tempdashboard back
$('#tempdashboard_' + dashboard.id).children().appendTo(dashboard.element);
$('#tempdashboard_' + dashboard.id).remove();
};
widget.openSettings = function() {
dashboard.log('entering openSettings function',1);
widget.element.find('.' + opts.widgetContentClass).load(widget.editurl);
};
// called when widget is initialized
if (widget.open) {
widget.openContent();
}
widget.initialized = true;
dashboard.log('widgetInitialized event thrown',2);
widget.element.trigger("widgetInitialized", {"widget":widget});
return widget;
};
// FIXME: can this be done easier??
function getLayout(id) {
dashboard.log('entering getLayout function',1);
var r = null;
var first = null;
if (typeof opts.layouts != 'undefined') {
$.each(opts.layouts,function(i, item) {
if (i == 0) { first = item; }
if (item.id == id) {
r = item;
}
});
}
if (r == null) { r = first }
return r;
}
$('#' + dashboard.id + ' .menutrigger').live('click', function() {
dashboard.log('widgetOpenMenu event thrown for widget ' + widget.id,2);
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
wi.element.trigger('widgetOpenMenu', {"widget":wi});
return false;
});
// add event handlers to the menu
$('#' + dashboard.id + ' .' + opts.widgetFullScreenClass).live('click',function(e) {
// close the menu
dashboard.log('widgetCloseMenu event thrown for widget ' + widget.id,2);
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
wi.element.trigger('widgetCloseMenu', {"widget":wi});
if (wi.fullscreen) {
dashboard.log('widgetCloseFullScreen event thrown for widget ' + wi.id,2);
wi.element.trigger('widgetCloseFullScreen', {"widget":wi});
} else {
dashboard.log('widgetOpenFullScreen event thrown for widget ' + wi.id,2);
wi.element.trigger('widgetOpenFullScreen', {"widget":wi});
}
return false;
});
$('#' + dashboard.id + ' .controls li').live('click',function(e) {
// close the menu
dashboard.log('widgetCloseMenu event thrown for widget ' + widget.id,2);
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
wi.element.trigger('widgetCloseMenu', {"widget":wi});
// use the class on the li to determine what action to trigger
dashboard.log($(this).attr('class') + ' event thrown for widget ' + widget.id,2);
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
wi.element.trigger($(this).attr('class'), {"widget":wi});
return false;
});
// add the menu events (by default triggers are connected in dashboard_jsonfeed)
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetCloseMenu',function(e,o) {
dashboard.log("Closing menu " + $(this).attr("id"),1);
o.widget.closeMenu();
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpenMenu',function(e,o) {
dashboard.log("Opening menu " + $(this).attr("id"),1);
o.widget.openMenu();
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetDelete',function(e,o) {
if (confirm(opts.deleteConfirmMessage)) {
dashboard.log("Removing widget " + $(this).attr("id"),1);
o.widget.remove();
}
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetRefresh',function(e,o) {
o.widget.refreshContent();
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetSetTitle',function(event, o) {
o.widget.setTitle(o.title);
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetClose',function(e,o) {
dashboard.log("Closing widget " + $(this).attr("id"),1);
o.widget.closeContent();
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpen',function(e,o) {
dashboard.log("Opening widget " + $(this).attr("id"),1);
o.widget.openContent();
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetShow',function() {
$(this).find('.' + opts.widgetContentClass).show();
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetHide',function() {
$(this).find('.' + opts.widgetContentClass).hide();
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetAddMetadataValue',function(e,o) {
dashboard.log("Changing metadata for widget " + $(this).attr("id") + ", metadata name: " + o.name + ", value: " + o.value, 1);
o.widget.addMetadataValue(o.name, o.value);
});
// Define a toggle event when clicking at the header
$('#' + dashboard.id + ' .' + opts.widgetTitleClass).live('click',function(e) {
dashboard.log("Click on the header detected for widget " + $(this).attr("id"),1);
if (!$(this).hasClass('noclick')) {
var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id"));
if (wi.open) {
dashboard.log('widgetClose event thrown for widget ' + wi,2);
wi.element.trigger('widgetClose', {"widget":wi});
} else {
dashboard.log('widgetOpen event thrown for widget ' + wi,2);
wi.element.trigger('widgetOpen', {"widget":wi});
}
}
return false;
});
$('#' + dashboard.id + ' .' + opts.widgetHeaderClass).live('mouseover',function () {
$(this).find('.' + opts.iconsClass).removeClass("hidden");
});
$('#' + dashboard.id + ' .' + opts.widgetHeaderClass).live('mouseout', function () {
$(this).find('.' + opts.iconsClass).addClass("hidden");
});
$('body').click(function() {
$('.' + opts.menuClass).hide();
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpenFullScreen',function(e,o) {
o.widget.openFullscreen();
});
$('.' + opts.widgetClass).live('widgetCloseFullScreen',function(e,o) {
o.widget.closeFullscreen();
});
$('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetEdit',function(e,o) {
o.widget.openSettings();
});
if ($('#' + addOpts.dialogId).length == 0) dashboard.log('Unable to find ' + addOpts.dialogId,5);
$('#' + addOpts.dialogId).dialog({
autoOpen: false,
height: 414,
width: 550,
modal: true,
buttons: {
Cancel: function() {
$(this).dialog('close');
}
},
close: function() {
//close
}
});
if ($('#' + layoutOpts.dialogId).length == 0) dashboard.log('Unable to find ' + layoutOpts.dialogId,5);
$('#' + layoutOpts.dialogId).dialog({
autoOpen: false,
height: 300,
width: 600,
modal: true
});
$('.' + layoutOpts.openDialogClass).live('click', function(){
dashboard.log('dashboardOpenLayoutDialog event thrown',2);
dashboard.element.trigger("dashboardOpenLayoutDialog");
return false;
});
dashboard.element.live('dashboardOpenLayoutDialog', function(){
dashboard.log('Opening dialog ' + layoutOpts.dialogId,1);
$('#' + layoutOpts.dialogId).dialog('open');
// add the layout images
var h = $('#' + layoutOpts.dialogId).find('.' + layoutOpts.layoutClass);
h.empty();
if (h.children().length == 0) {
dashboard.log('Number of layouts : ' + opts.layouts.length,1);
$.each(opts.layouts,function(i, item) {
dashboard.log('Applying template : ' + layoutOpts.layoutTemplate,1);
if ($('#' + layoutOpts.layoutTemplate).length == 0) dashboard.log('Template "' + layoutOpts.layoutTemplate + ' not found',5);
h.append(tmpl($('#' + layoutOpts.layoutTemplate).html(), item));
});
}
// set the selected class for the selected layout
$('.' + layoutOpts.selectLayoutClass).removeClass(layoutOpts.selectedLayoutClass);
$('#' + dashboard.layout.id).addClass(layoutOpts.selectedLayoutClass);
bindSelectLayout();
});
dashboard.element.live('dashboardStateChange', function(){
if (typeof opts.stateChangeUrl != 'undefined' && opts.stateChangeUrl != null && opts.stateChangeUrl != '') {
$.ajax({type: 'POST',
url: opts.stateChangeUrl,
data: { dashboard: dashboard.element.attr("id"), settings: dashboard.serialize() },
success: function(data){
if (data == "NOK" || data.indexOf('<response>NOK</response>') != -1){
dashboard.log('dashboardSaveFailed event thrown',2);
dashboard.element.trigger("dashboardSaveFailed");
} else {
dashboard.log('dashboardSuccessfulSaved event thrown',2);
dashboard.element.trigger("dashboardSuccessfulSaved");
}
},
error: function(XMLHttpRequest, textStatus, errorThrown){
dashboard.log('dashboardSaveFailed event thrown',2);
dashboard.element.trigger("dashboardSaveFailed");
},
dataType: "text"
});
}
});
dashboard.element.live('dashboardCloseLayoutDialog', function(){
// close the dialog
$('#' + layoutOpts.dialogId).dialog('close');
});
// FIXME: why doesn't the live construct work in this case
function bindSelectLayout() {
if ($('.' + layoutOpts.selectLayoutClass).length == 0) dashboard.log('Unable to find ' + layoutOpts.selectLayoutClass,5);
$('.' + layoutOpts.selectLayoutClass).bind('click', function(e){
var currentLayout = dashboard.layout;
dashboard.log('dashboardCloseLayoutDialog event thrown',2);
dashboard.element.trigger('dashboardCloseLayoutDialog');
// Now set the new layout
var newLayout = getLayout($(this).attr("id"));
dashboard.layout = newLayout;
// remove the class of the old layout
if (typeof opts.layoutClass != 'undefined') {
dashboard.element.find('.' + opts.layoutClass).removeClass(currentLayout.classname).addClass(newLayout.classname);
fixSortableColumns();
// check if there are widgets in hidden columns, move them to the first column
if ($('.' + opts.columnClass).length == 0) dashboard.log('Unable to find ' + opts.columnClass,5);
dashboard.element.find('.' + opts.columnClass).each(function() {
if ($(this).css("display") == "none") {
// move the widgets to the first column
$(this).children().appendTo(dashboard.element.find('.' + opts.columnClass + ':first'));
}
$('.emptycolumn').remove();
// add the text to the empty columns
$('.' + opts.columnClass).each(function() {
if ($(this).children().length == 0) {
$(this).html('<div class="emptycolumn">' + opts.emptyColumnHtml + '</div>');
}
});
});
} else {
// set the new layout, but first move the dashboard to a temp
var temp = $('<div style="display:none" id="tempdashboard"></div>');
temp.appendTo($("body"));
dashboard.element.children().appendTo(temp);
// reload the dashboard
dashboard.init();
}
// throw an event upon changing the layout.
dashboard.log('dashboardChangeLayout event thrown',2);
dashboard.element.trigger('dashboardLayoutChanged');
});
return false;
}
$('.' + addOpts.selectCategoryClass).live('click', function(){
dashboard.log('addWidgetDialogSelectCategory event thrown',2);
dashboard.element.trigger('addWidgetDialogSelectCategory', {"category":$(this)});
return false;
});
dashboard.element.live('addWidgetDialogSelectCategory', function(e, obj){
// remove the category selection
$('.' + addOpts.selectCategoryClass).removeClass(addOpts.selectedCategoryClass);
// empty the widgets div
$('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).empty();
// select the category
$(obj.category).addClass(addOpts.selectedCategoryClass);
// get the widgets
url = dashboard.widgetCategories[$(obj.category).attr("id")];
dashboard.log('Getting JSON feed : ' + url,1);
$.getJSON(url, {"cache":true}, function(json) {
// load the widgets from the category
if (json.data == 0) dashboard.log('Empty data returned',3);
$.each(json.data, function(i,item){
dashboard.widgetsToAdd[item.id] = item;
dashboard.log('Applying template : ' + addOpts.widgetTemplate,1);
if ($('#' + addOpts.widgetTemplate).length == 0) dashboard.log('Template "' + addOpts.widgetTemplate + ' not found',5);
var html = tmpl($('#' + addOpts.widgetTemplate).html(), item);
$('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).append(html);
});
});
dashboard.log('addWidgetDialogWidgetsLoaded event thrown',2);
dashboard.element.trigger('addWidgetDialogWidgetsLoaded');
});
$('.' + addOpts.addWidgetClass).live('click', function(){
var widget = dashboard.widgetsToAdd[$(this).attr("id").replace('addwidget','')];
dashboard.log('dashboardAddWidget event thrown',2);
dashboard.element.trigger('dashboardAddWidget', {"widget":widget});
dashboard.log('dashboardCloseWidgetDialog event thrown',2);
dashboard.element.trigger('dashboardCloseWidgetDialog');
return false;
});
$('.' + addOpts.openDialogClass).live('click', function(){
dashboard.log('dashboardOpenWidgetDialog event thrown',2);
dashboard.element.trigger('dashboardOpenWidgetDialog');
return false;
});
dashboard.element.live('dashboardCloseWidgetDialog', function(){
// close the dialog
$('#' + addOpts.dialogId).dialog('close');
});
dashboard.element.live('dashboardOpenWidgetDialog', function(){
//remove existing categories/widgets from the DOM, to prevent duplications
$('#' + addOpts.dialogId).find('.' + addOpts.categoryClass).empty();
$('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).empty();
dashboard.log('Opening dialog ' + addOpts.dialogId,1);
$('#' + addOpts.dialogId).dialog('open');
dashboard.log('Getting JSON feed : ' + addOpts.widgetDirectoryUrl,1);
$.getJSON(addOpts.widgetDirectoryUrl, function(json) {
if (json.category == 0) dashboard.log('Empty data returned',3);
$.each(json.category, function(i,item){
// Add the categories to the dashboard
dashboard.widgetCategories[item.id] = item.url;
dashboard.log('Applying template : ' + addOpts.categoryTemplate,1);
if ($('#' + addOpts.categoryTemplate).length == 0) dashboard.log('Template "' + addOpts.categoryTemplate + ' not found',5);
var html = tmpl($('#' + addOpts.categoryTemplate).html(),item);
$('#' + addOpts.dialogId).find('.' + addOpts.categoryClass).append(html);
});
dashboard.log('addWidgetDialogCategoriesLoaded event thrown',2);
dashboard.element.trigger('addWidgetDialogCategoriesLoaded');
dashboard.log('addWidgetDialogSelectCategory event thrown',2);
dashboard.element.trigger('addWidgetDialogSelectCategory', {"category":$('#' + addOpts.dialogId).find('.' + addOpts.categoryClass + '>li:first')});
});
});
return dashboard;
};
// Public static properties of dashboard. Default settings.
$.fn.dashboard.defaults = {
debuglevel:3,
json_data: {},
loadingHtml: '<div class="loading"><img alt="Loading, please wait" src="../themes/default/loading.gif" /><p>Loading...</p></div>',
emptyColumnHtml: 'Drag your widgets here',
widgetTemplate: 'widgettemplate',
columnPrefix: 'column-',
opacity:"0.2",
deleteConfirmMessage: "Are you sure you want to delete this widget?",
widgetNotFoundHtml: "The content of this widget is not available anymore. You may remove this widget.",
columnClass: 'column',
widgetClass: 'widget',
menuClass: 'controls',
widgetContentClass: 'widgetcontent',
widgetTitleClass: 'widgettitle',
widgetHeaderClass: 'widgetheader',
widgetFullScreenClass: 'widgetopenfullscreen',
iconsClass: 'icons',
stateChangeUrl: '',
addWidgetSettings: {
openDialogClass: 'openaddwidgetdialog',
addWidgetClass: 'addwidget',
selectCategoryClass: 'selectcategory',
selectedCategoryClass: 'selected',
categoryClass: 'categories',
widgetClass: 'widgets',
dialogId: 'addwidgetdialog',
categoryTemplate: 'categorytemplate',
widgetTemplate: 'addwidgettemplate'
},
editLayoutSettings: {
dialogId: 'editLayout',
layoutClass: 'layoutselection',
selectLayoutClass: 'layoutchoice',
selectedLayoutClass: 'selected',
openDialogClass: 'editlayout',
layoutTemplate: 'selectlayouttemplate'
}
};
// Default widget settings.
$.fn.dashboard.widget = {
defaults: {
open: true,
fullscreen: false,
loaded: false,
url: '',
metadata: {}
}
};
})(jQuery); // end of closure
// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
var cache = {};
this.tmpl = function tmpl(str, data){
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
// Provide some basic currying to the user
return data ? fn( data ) : fn;
};
})();
@@ -0,0 +1,36 @@
.column {
float: left;
padding-bottom: 100px;
}
.portlet {
margin: 0 1em 1em 0;
}
.portlet-header {
margin: 0.3em;
padding-bottom: 4px;
padding-left: 0.2em;
}
.portlet-header:hover {
cursor: move;
}
.portlet-header .ui-icon {
float: right;
}
.portlet-content {
padding: 0.4em;
}
.ui-sortable-placeholder {
border: 1px dotted black;
visibility: visible !important;
height: 50px !important;
}
.ui-sortable-placeholder * {
visibility: hidden;
}
@@ -1,85 +0,0 @@
<script type="text/html" id="categorytemplate">
<li id="<%= id %>" class="selectcategory"><button><%= title %> (<%= amount %>)</button></li>
</script>
<script type="text/html" id="widgettemplate">
<div class="ui-widget ui-corner-all ui-widget-content widget" id="<%= id %>" title="<%= title %>">
<div class="ui-widget-header ui-corner-all widgetheader">
<span class="widgettitle"><%= title %></span>
<span class="right icons hidden">
<span class="ui-icon ui-icon-newwin widgetopenfullscreen"></span>
<span class="ui-icon ui-icon-arrowthickstop-1-s menutrigger"></span>
<span class="hiddenmenu">
<ul style="top: 13px;" class="hidden controls ui-widget-header">
<li class="widgetClose">
<span class="ui-icon ui-icon-minus"></span>
<a class="minimization" href="#">Minimize</a>
</li>
<li class="widgetOpen">
<span class="ui-icon ui-icon-extlink"></span>
<a class="minimization" href="#">Maximize</a>
</li>
<li class="widgetDelete">
<span class="ui-icon ui-icon-close"></span>
<a class="delete" href="#">Delete</a>
</li>
<!-- This could be implemented -->
<!--
<li class="widgetEdit">
<span class="ui-icon ui-icon-tag"></span>
<a class="no_target" href="#">Edit</a>
</li>
-->
<li class="widgetRefresh">
<span class="ui-icon ui-icon-arrowrefresh-1-w"></span>
<a class="no_target" href="#">Refresh</a>
</li>
</ul>
</span>
</span>
</div>
<div class="widgetcontent">
</div>
</div>
</script>
<script type="text/html" id="selectlayouttemplate">
<li class="layoutchoice" id="<%= id %>" style="background-image: url('<%= image %>')"></li>
</script>
<script type="text/html" id="addwidgettemplate">
<li class="widgetitem">
<img src="<%= image %>" alt="" height="60" width="120">
<div class="add-button">
<input class="macro-button-add addwidget" id="addwidget<%= id %>" value="Add it Now" type="button"><br>
<input class="macro-hidden-uri" value="<%= url %>" type="hidden">
</div>
<!-- // .add-button -->
<h3><a href=""><%= title %></a></h3>
<p>By <%= creator %></p>
<p><%= description %></p>
</li>
</script>
<div class="dialog" id="addwidgetdialog" title="Widget Directory">
<ul class="categories">
</ul>
<div class="panel-body">
<ol id="category-all" class="widgets">
</ol>
</div>
</div>
<div class="dialog" id="editLayout" title="Edit layout">
<div class="panel-body" id="layout-dialog">
<p><strong>Choose dashboard layout</strong></p>
<ul class="layoutselection">
</ul>
</div>
</div>
@@ -10,39 +10,70 @@ openerp.base.form.Board = openerp.base.form.Widget.extend({
},
start: function() {
this._super.apply(this, arguments);
this.$element.html(QWeb.render(this.template));
},
render: function() {
var self = this;
jQuery('body').append(
jQuery('<div>', {'id': 'dashboard_template'}).load('/base_dashboard/static/src/dashboard_template.html',self.on_loaded).hide()
)
},
on_loaded: function() {
this.$element.html(QWeb.render(this.template));
var $dashboard = this.$element.find('#dashboard');
var children = this.node.children;
var board = jQuery('#dashboard').dashboard({
layoutClass:'layout'
});
board.init();
for(var ch = 0; ch < children.length; ch++) {
var ch_widgets = children[ch].children;
for(var chld = 0; chld < ch_widgets.length; chld++) {
var widget_type = ch_widgets[chld].tag;
var child_index = widget_type == 'action' ? chld : ch;
var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, ch_widgets[chld], board, child_index);
widget.start();
for(var ch=0; ch < children.length; ch++) {
var node = children[ch];
var widget;
if(node.tag.indexOf('child') >= 0) {
widget = new (openerp.base.form.widgets.get_object('child')) (this.view, node, $dashboard);
} else {
//Vpaned
widget = new (openerp.base.form.widgets.get_object(node.tag)) (this.view, node, $dashboard);
}
widget.start();
}
}
jQuery('.column').css('width', 100/children.length+'%');
},
});
openerp.base.form.Dashbar = openerp.base.form.Widget.extend({
init: function(view, node, dashboard) {
this._super(view, node, dashboard);
this.dashboard = dashboard;
this.template = 'Portlet'
},
start: function() {
var $dashboard = this.dashboard;
var children = this.node.children;
$dashboard.append(QWeb.render(this.template, {widget: this, 'children': children}))
for(var chld=0; chld < children.length;chld++) {
var child = children[chld];
var widget = new (openerp.base.form.widgets.get_object(child.tag)) (this.view, child);
widget.start()
}
$( ".column" ).sortable({
connectWith: ".column",
scroll: false
});
$( ".portlet" ).addClass( "ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" )
.find( ".portlet-header" )
.addClass( "ui-widget-header ui-corner-all" )
.end()
.find( ".portlet-content" );
$( ".portlet-header .ui-icon" ).click(function() {
$( this ).toggleClass( "ui-icon-minusthick" ).toggleClass( "ui-icon-plusthick" );
$( this ).parents( ".portlet:first" ).find( ".portlet-content" ).toggle();
});
$( ".column" ).disableSelection();
}
})
openerp.base.form.Action = openerp.base.form.Widget.extend({
init: function(view, node, board, child_index) {
this._super(view, node, board, child_index);
this.board = board;
this.child_index = child_index;
init: function(view, node) {
this._super(view, node);
},
start: function() {
this._super.apply(this, arguments);
@@ -54,26 +85,17 @@ openerp.base.form.Action = openerp.base.form.Widget.extend({
on_load_action: function(result) {
var action = result.action;
action.search_view = false;
action.no_sidebar = true;
action.search_view_id = false;
action.flags = {
search_view : false,
sidebar : false,
views_switcher : false,
action_buttons : false
}
var node_attrs = this.node.attrs;
var get_column = ['first', 'second', 'third'];
var board_element = this.board.element.find('[id=column-'+get_column[this.child_index]+']');
this.board.addWidget({
'id': node_attrs.name,
'title': node_attrs.string,
}, board_element);
var content_id = node_attrs.name+'-widgetcontent';
this.board.getWidget(node_attrs.name).element.find('.widgetcontent').attr('id',content_id)
action_manager = new openerp.base.ActionManager(this.session, content_id);
var content_id = 'portlet-content-'+node_attrs.name;
var action_manager = new openerp.base.ActionManager(this.session, content_id);
action_manager.start();
this.board.getWidget(node_attrs.name).url = action_manager.do_action(action);
action_manager.do_action(action);
}
})
@@ -99,6 +121,7 @@ openerp.base.form.Vpaned = openerp.base.form.Widget.extend({
})
openerp.base.form.widgets.add('hpaned', 'openerp.base.form.Board');
openerp.base.form.widgets.add('child', 'openerp.base.form.Dashbar');
openerp.base.form.widgets.add('vpaned', 'openerp.base.form.Vpaned');
openerp.base.form.widgets.add('action', 'openerp.base.form.Action');
}
}
@@ -1,12 +1,17 @@
<template>
<t t-name="Board">
<div id="dashboard" class="dashboard">
<!-- this HTML covers all layouts. The 5 different layouts are handled by setting another layout classname -->
<div class="layout">
<div id="column-first" class="column first column-first"></div>
<div id="column-second" class="column second column-second"></div>
<div id="column-third" class="column third column-third"></div>
</div>
<div id="dashboard" class="demo">
</div>
</t>
<t t-name="Portlet">
<div t-att-id="widget.node.tag" class="column">
<div class="portlet" t-foreach="children" t-as="child" t-att-id="child.attrs.name">
<div class="portlet-header">
<t t-esc="child.attrs.string"/>
<span class="ui-icon ui-icon-minusthick"></span>
</div>
<div class="portlet-content" t-att-id="'portlet-content-'+child.attrs.name"></div>
</div>
</div>
</t>
</template>
+2 -2
View File
@@ -3,8 +3,8 @@
"version": "2.0",
"depends": ['base'],
"js": [
'static/lib/dhtmlxGantt/codebase/dhtmlxcommon.js',
'static/lib/dhtmlxGantt/codebase/dhtmlxgantt.js',
'static/lib/dhtmlxGantt/sources/dhtmlxcommon.js',
'static/lib/dhtmlxGantt/sources/dhtmlxgantt.js',
'static/src/js/gantt.js'
],
"css": ['static/lib/dhtmlxGantt/codebase/dhtmlxgantt.css'],
+5 -178
View File
@@ -1,184 +1,11 @@
import glob, os
from xml.etree import ElementTree
import math
import simplejson
import openerpweb
import time
import datetime
from base.controllers.main import Xml2Json
from base.controllers.main import View
COLOR_PALETTE = ['#f57900', '#cc0000', '#d400a8', '#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400',
'#fcaf3e', '#ef2929', '#ff00c9', '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f',
'#ff8e00', '#ff0000', '#b0008c', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
'#905000', '#9b0000', '#840067', '#510090', '#0000c9', '#009b00', '#9abe00', '#ffc900', ]
_colorline = ['#%02x%02x%02x' % (25 + ((r + 10) % 11) * 23, 5 + ((g + 1) % 11) * 20, 25 + ((b + 4) % 11) * 23) for r in range(11) for g in range(11) for b in range(11) ]
def choice_colors(n):
if n > len(COLOR_PALETTE):
return _colorline[0:-1:len(_colorline) / (n + 1)]
elif n:
return COLOR_PALETTE[:n]
return []
class GanttView(openerpweb.Controller):
class GanttView(View):
_cp_path = "/base_gantt/ganttview"
date_start = None
date_delay = None
date_stop = None
color_field = None
day_length = 8
fields = {}
events = []
calendar_fields = {}
ids = []
model = ''
domain = []
context = {}
event_res = []
colors = {}
color_values = []
@openerpweb.jsonrequest
def load(self, req, model, view_id):
m = req.session.model(model)
r = m.fields_view_get(view_id, 'gantt')
r["arch"] = Xml2Json.convert_to_structure(r["arch"])
return {'fields_view':r}
@openerpweb.jsonrequest
def get_events(self, req, **kw):
fields_view = self.fields_view_get(req, model, view_id, 'gantt')
return {'fields_view':fields_view}
self.model = kw['model']
self.fields = kw['fields']
self.day_length = kw['day_length']
self.calendar_fields = kw['calendar_fields']
self.color_field = kw.get('color_field') or self.color_field or None
self.fields[self.color_field] = ""
self.colors = kw.get('colors') or {}
self.text = self.calendar_fields['text']['name']
self.date_start = self.calendar_fields['date_start']['name']
self.fields[self.date_start] = ""
if self.calendar_fields.get('date_stop'):
self.date_stop = self.calendar_fields['date_stop']['name']
self.fields[self.date_stop] = ""
if self.calendar_fields.get('date_delay'):
self.date_delay = self.calendar_fields['date_delay']['name']
self.fields[self.date_delay] = ""
if self.calendar_fields.get('parent'):
self.parent = self.calendar_fields['parent']['name']
self.fields[self.parent] = ""
model = req.session.model(self.model)
event_ids = model.search([])
return self.create_event(event_ids, model)
def create_event(self, event_ids, model):
self.events = model.read(event_ids, self.fields.keys())
result = []
for evt in self.events:
event_res = {}
key = evt[self.color_field]
name = key
value = key
if isinstance(key, list): # M2O, XMLRPC returns List instead of Tuple
name = key[0]
value = key[-1]
evt[self.color_field] = key = key[-1]
if isinstance(key, tuple): # M2O
value, name = key
self.colors[key] = (name, value, None)
st_date = evt.get(self.date_start)
if st_date:
self.set_format(st_date)
if self.date_delay:
duration = evt.get(self.date_delay)
else:
en_date = evt.get(self.date_stop)
duration = (time.mktime(time.strptime(en_date, self.format))-\
time.mktime(time.strptime(st_date, self.format)))/ (60 * 60)
if duration > self.day_length :
d = math.floor(duration / 24)
h = duration % 24
duration = d * self.day_length + h
event_res = {}
event_res['start_date'] = st_date
event_res['duration'] = duration
event_res['text'] = evt.get(self.text)
event_res['id'] = evt['id']
event_res['parent'] = evt.get(self.parent)
result.append(event_res)
colors = choice_colors(len(self.colors))
for i, (key, value) in enumerate(self.colors.items()):
self.colors[key] = [value[0], value[1], colors[i]]
return {'result': result,'sidebar': self.colors}
def set_format(self, st_date):
if len(st_date) == 10 :
self.format = "%Y-%m-%d"
else :
self.format = "%Y-%m-%d %H:%M:%S"
return
def check_format(self, date):
if self.format == "%Y-%m-%d %H:%M:%S":
date = date + " 00:00:00"
return date
@openerpweb.jsonrequest
def on_event_resize(self, req, **kw):
if self.date_delay:
key = self.date_delay
value = kw['duration']
else:
key = self.date_stop
value = self.check_format(kw['end_date'])
try:
model = req.session.model(self.model)
res = model.write(kw['id'], {key : value})
except Exception, e:
print "eeeeeeeeeeeeeeeeeeeeeeeeeee",e
return True
@openerpweb.jsonrequest
def on_event_drag(self, req, **kw):
start_date = self.check_format(kw['start_date'])
if self.date_delay:
key = self.date_delay
value = kw['duration']
else:
key = self.date_stop
value = self.check_format(kw['end_date'])
try:
model = req.session.model(self.model)
res = model.write(kw['id'], {self.date_start : start_date, key : value})
except Exception, e:
print "eeeeeeeeeeeeeeeeeeeeeeeeeee",e
return True
@openerpweb.jsonrequest
def reload_gantt(self, req, **kw):
model = req.session.model(kw['model'])
if (kw['domain']):
domain = (kw['color_field'],'in', kw['domain'])
event_ids = model.search([domain])
else:
event_ids = model.search([])
return self.create_event(event_ids, model)
@@ -173,8 +173,9 @@ GanttProjectInfo.prototype.getTaskByIdInTree = function(parentTask, id)
* @type: public
* @topic: 0
*/
function GanttTaskInfo(id, name, est, duration, percentCompleted, predecessorTaskId)
function GanttTaskInfo(id, name, est, duration, percentCompleted, predecessorTaskId, color)
{
this.color = color || "white";
this.Id = id;
this.Name = name;
this.EST = est;
@@ -348,7 +349,7 @@ GanttProject.prototype.create = function()
* @type: public
* @topic: 0
*/
function GanttChart()
function GanttChart(day_length)
{
this.Error = new GanttError();
this.dhtmlXMLSenderObject = new dhtmlXMLSenderObject(this);
@@ -356,7 +357,7 @@ function GanttChart()
//settings
this.heightTaskItem = 12;
this.dayInPixels = 24;
this.hoursInDay = 8;
this.hoursInDay = day_length;
this._showTreePanel = true;
this._showTooltip = true;
this.isShowDescTask = false;
@@ -4869,11 +4870,13 @@ GanttTask.prototype.createTaskItem = function()
cellTblTask.height = this.Chart.heightTaskItem + "px";
cellTblTask.width = this.TaskInfo.PercentCompleted + "%";
cellTblTask.style.lineHeight = "1px";
cellTblTask.style.backgroundColor = self.TaskInfo.color;
var imgPr = document.createElement("img");
imgPr.style.width = (this.TaskInfo.PercentCompleted * this.TaskInfo.Duration * this.Chart.hourInPixelsWork) / 100 + "px";
imgPr.style.height = this.Chart.heightTaskItem + "px";
cellTblTask.appendChild(imgPr);
imgPr.src = this.Chart.imgs + "progress_filled.png";
//imgPr.src = this.Chart.imgs + "progress_filled.png";
}
if (this.TaskInfo.PercentCompleted != 100)
@@ -4943,9 +4946,9 @@ GanttTask.prototype.createTaskItem = function()
}
var taskClick = function() {
self.Chart.callEvent("onTaskClick", [self]);
self.Chart.callEvent("onTaskDblClick", [self]);
};
this.addEvent(divMove, 'click', taskClick, false);
this.addEvent(divMove, 'dblclick', taskClick, false);
if (this.Chart.isEditable)
{
+285 -166
View File
@@ -7,7 +7,8 @@ QWeb.add_template('/base_gantt/static/src/xml/base_gantt.xml');
openerp.base.views.add('gantt', 'openerp.base_gantt.GanttView');
openerp.base_gantt.GanttView = openerp.base.Controller.extend({
init: function(view_manager, session, element_id, dataset, view_id) {
init: function(view_manager, session, element_id, dataset, view_id) {
this._super(session, element_id);
this.view_manager = view_manager;
this.dataset = dataset;
@@ -16,207 +17,326 @@ openerp.base_gantt.GanttView = openerp.base.Controller.extend({
this.fields_views = {};
this.widgets = {};
this.widgets_counter = 0;
this.fields = {};
this.datarecord = {};
this.fields = this.dataset.fields ? this.dataset.fields: {};
this.ids = this.dataset.ids;
this.name = "";
this.date_start = "";
this.date_delay = "";
this.date_stop = "";
this.color_field = "";
this.day_lenth = 8;
this.colors = [];
this.color_values = [];
this.calendar_fields = {};
this.info_fields = [];
this.domain = this.dataset._domain ? this.dataset._domain: [];
this.context = {};
},
do_show: function () {
// TODO: re-trigger search
this.$element.show();
},
do_hide: function () {
this.$element.hide();
},
start: function() {
this.rpc("/base_gantt/ganttview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
this.rpc("/base_gantt/ganttview/load",
{"model": this.model, "view_id": this.view_id}, this.on_loaded);
},
on_loaded: function(data) {
this.fields_view = data.fields_view;
var self = this;
this.fields_view = data.fields_view;
this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
this.view_id = this.fields_view.view_id;
this.date_start = this.fields_view.arch.attrs.date_start;
this.date_delay = this.fields_view.arch.attrs.date_delay;
this.date_stop = this.fields_view.arch.attrs.date_stop;
this.color_field = this.fields_view.arch.attrs.color;
this.color_field = this.fields_view.arch.attrs.color;
this.day_length = this.fields_view.arch.attrs.day_length || 8;
this.colors = this.fields_view.arch.attrs.colors;
this.fields = this.fields_view.fields;
this.text = this.fields_view.arch.children[0].children[0].attrs.name;
this.parent = this.fields_view.arch.children[0].attrs.link;
this.calendar_fields['parent'] = {'name': this.parent};
this.calendar_fields['date_start'] = {'name': this.date_start};
this.calendar_fields['text'] = {'name': this.text};
if(this.date_delay)
this.calendar_fields['date_delay'] = {'name': this.date_delay};
if(this.date_stop)
this.calendar_fields['date_stop'] = {'name': this.date_stop};
this.format = "yyyy-MM-dd";
self.create_gantt();
self.get_events();
this.calendar_fields['day_length'] = this.day_length;
this.rpc('/base_gantt/ganttview/get_events',
{'model': this.model,
'fields': this.fields,
'color_field': this.color_field,
'day_length': this.day_length,
'calendar_fields': this.calendar_fields,
'colors': this.colors,
'info_fields': this.info_fields
},
function(res) {
self.create_gantt();
self.load_event(res);
})
this.$element.html(QWeb.render("GanttView", {"view": this, "fields_view": this.fields_view}));
},
convert_date_format: function(date) {
date=date+"";
if(typeof (date)!="string"||date.length===0){
return null;
}
var iso=date.split("-");
if(iso.length===0){
return null;
}
var day = iso[2];
var iso_hours = day.split(' ');
if (iso_hours.length > 1) {
day = iso_hours[0];
var iso_date_hours = iso_hours[1].split(':')
var new_date = new Date(iso[0], iso[1] - 1, day);
new_date.setHours(iso_date_hours[0]);
new_date.setMinutes(iso_date_hours[1]);
new_date.setSeconds(iso_date_hours[2]);
}
else {
var new_date = new Date(iso[0], iso[1] - 1, day);
}
new_date.setFullYear(iso[0]);
new_date.setMonth(iso[1]-1);
new_date.setDate(day);
return new_date;
},
create_gantt: function() {
ganttChartControl = new GanttChart();
ganttChartControl = new GanttChart(this.day_length);
ganttChartControl.setImagePath("/base_gantt/static/lib/dhtmlxGantt/codebase/imgs/");
ganttChartControl.setEditable(true);
ganttChartControl.showTreePanel(true);
ganttChartControl.showContextMenu(true);
ganttChartControl.showDescTask(true,'d,s-f');
ganttChartControl.showDescProject(true,'n,d');
},
load_event: function(res) {
var self = this
var result = res.result;
var sidebar = res.sidebar;
var project_id = new Array();
var project = new Array();
var j = -1;
var self = this;
for (i in result) {
var parent_id = result[i]['parent'][0];
var parent_name = result[i]['parent'][1];
if (jQuery.inArray(parent_id, project_id) == -1){
if (parent_id == undefined){
parent_name = "";
}
j = j + 1;
project[j] = new GanttProjectInfo(parent_id, parent_name, new Date(2011, 1, 1));
project_id[j] = parent_id;
}
var id = result[i]['id'];
var text = result[i]['text'];
var start_date = this.convert_date_format(result[i]['start_date']);
var duration = result[i]['duration'];
var task = new GanttTaskInfo(id, text, start_date, duration, 100, "");
k = project_id.indexOf(parent_id);
project[k].addTask(task);
}
for (i in project_id){
ganttChartControl.addProject(project[i]);
}
ganttChartControl.create("GanttDiv");
ganttChartControl.attachEvent("onTaskEndResize", function(task) {self.on_task_end_resize(task);})
ganttChartControl.attachEvent("onTaskEndDrag", function(task) {self.on_task_end_drag(task);})
//Create Sidebar
if (jQuery('#cal-sidebar-option').length == 0){
jQuery('#gantt-sidebar').append(
jQuery('<table>',{'width':'100%','cellspacing': 0, 'cellpadding': 0, 'id':'cal-sidebar-option'})
)
for(s in sidebar) {
jQuery('#cal-sidebar-option').append(
jQuery('<tr>').append(
jQuery('<td>').append(
jQuery('<div>')
.append(
jQuery('<input>',
{
'type': 'checkbox',
'id':sidebar[s][0],
'value':sidebar[s][0]
}).bind('click',function(){
self.reload_gantt(self.color_field,self.model)
}),
sidebar[s][1]
)
.css('background-color',sidebar[s][sidebar[s].length-1])
)
)
)
},
get_events: function() {
var self = this;
this.dataset.read_ids(this.dataset.ids, {}, function(result) {
self.load_event(result);
});
},
load_event: function(events) {
var self = this;
var result = events;
var project = {};
var project_smalldate = {};
var proj_id = [];
var proj_id_text = [];
COLOR_PALETTE = ['#ccccff', '#cc99ff', '#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400',
'#fcaf3e', '#ef2929', '#ff00c9', '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f',
'#ff8e00', '#ff0000', '#b0008c', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
'#905000', '#9b0000', '#840067', '#510090', '#0000c9', '#009b00', '#9abe00', '#ffc900']
//Smallest date of child is parent start date
for (i in result){
var res = result[i];
if (res[this.date_start] != false){
var parent_id = res[this.parent][0] || res[this.parent];
var parent_name = res[this.parent][1];
var start_date = this.convert_str_date(res[this.date_start]);
if (project_smalldate[parent_id] == undefined){
project_smalldate[parent_id] = start_date;
proj_id.push(parent_id);
if (parent_name != undefined){
proj_id_text.push(res[this.parent]);
}
}
else{
if (start_date < project_smalldate[parent_id]){
project_smalldate[parent_id] = start_date;
}
}
}
}
//get parent text
if (parent_name == undefined){
var ajax = {
url: '/base/dataset/call',
async: false
};
this.rpc(ajax, {
model: this.dataset.model,
method: "name_get",
ids: proj_id,
args: ""
}, function(response) {
proj_id_text = response.result;
});
}
//create parents
for (i in proj_id){
var id = proj_id_text[i][0];
var text = proj_id_text[i][1];
project[id] = new GanttProjectInfo(id, text, project_smalldate[id]);
}
//create childs
var k = 0;
var color_box = {};
for (i in result) {
var res = result[i];
if (res[this.date_start] != false){
var parent_id = res[this.parent][0] || res[this.parent];
var id = res['id'];
var text = res[this.text];
var start_date = this.convert_str_date(res[this.date_start]);
var color = res[this.color_field][0] || res[this.color_field];
if (color_box[color] == undefined){
color_box[color] = COLOR_PALETTE[k];
k = k + 1;
}
if (this.date_stop != undefined){
if (res[this.date_stop] != false){
var stop_date = this.convert_str_date(res[this.date_stop]);
var duration= self.hours_between(start_date, stop_date);
}
else{
var duration = 0;
}
}
else{
var duration = res[this.date_delay];
}
if (duration == false)
duration = 0
task = new GanttTaskInfo(id, text, start_date, duration, 100, "", color_box[color]);
project[parent_id].addTask(task);
}
}
//Add parent
for (i in proj_id){
ganttChartControl.addProject(project[proj_id[i]]);
}
ganttChartControl.create("GanttDiv");
ganttChartControl.attachEvent("onTaskStartDrag", function(task) {self.on_drag_start(task);});
ganttChartControl.attachEvent("onTaskEndResize", function(task) {self.on_resize_drag_end(task, "resize");});
ganttChartControl.attachEvent("onTaskEndDrag", function(task) {self.on_resize_drag_end(task, "drag");});
ganttChartControl.attachEvent("onTaskDblClick", function(task) {self.open_popup(task);});
},
reload_gantt: function(color_field, model) {
var domain = [];
hours_between: function(date1, date2) {
var ONE_DAY = 1000 * 60 * 60 * 24;
var date1_ms = date1.getTime();
var date2_ms = date2.getTime();
var difference_ms = Math.abs(date1_ms - date2_ms);
d = Math.round(difference_ms / ONE_DAY);
h = Math.round((difference_ms % ONE_DAY)/(1000 * 60 * 60));
return (d * this.day_length) + h;
},
open_popup : function(task) {
var event_id = task.getId();
if (event_id) {
event_id = parseInt(event_id, 10);
var dataset_event_index = jQuery.inArray(event_id, this.ids);
} else {
var dataset_event_index = null;
}
this.dataset.index = dataset_event_index;
var element_id = _.uniqueId("act_window_dialog");
var dialog = jQuery('<div>',
{'id': element_id
}).dialog({
title: 'Gantt Chart',
modal: true,
minWidth: 800,
position: 'top'
});
var event_form = new openerp.base.FormView(this.view_manager, this.session, element_id, this.dataset, false);
event_form.start();
},
on_drag_start : function(task){
st_date = task.getEST();
if(st_date.getHours()){
self.hh = st_date.getHours();
self.mm = st_date.getMinutes();
}
},
on_resize_drag_end : function(task, evt){
var event_id = task.getId();
var data = {};
if (evt == "drag"){
full_date = task.getEST().set({hour: self.hh, minute : self.mm, second:0});
data[this.date_start] = this.convert_date_str(full_date);
}
if (this.date_stop != undefined){
tm = (task.getDuration() % this.day_length);
stp = task.getFinishDate().add(tm).hour();
data[this.date_stop] = this.convert_date_str(stp);
}else{
data[this.date_delay] = task.getDuration();
}
this.dataset.write(event_id, data, function(result) {});
},
do_show: function () {
this.$element.show();
},
do_hide: function () {
this.$element.hide();
},
convert_str_date: function (str){
if (str.length == 19){
this.format = "yyyy-MM-dd HH:mm:ss";
return openerp.base.parse_datetime(str);
} else if (str.length == 10){
this.format = "yyyy-MM-dd";
return openerp.base.parse_date(str);
} else if (str.length == 8){
this.format = "HH:mm:ss";
return openerp.base.parse_time(str);
}
throw "Unrecognized date/time format";
},
convert_date_str: function(full_date) {
if (this.format == "yyyy-MM-dd HH:mm:ss"){
return openerp.base.format_datetime(full_date);
} else if (this.format == "yyyy-MM-dd"){
return openerp.base.format_date(full_date);
} else if (this.format == "HH:mm:ss"){
return openerp.base.format_time(full_date);
}
throw "Unrecognized date/time format";
},
reload_gantt: function(domain) {
var self = this;
jQuery('input[type=checkbox]:checked','#cal-sidebar-option').each(function() {
domain.push(parseInt(jQuery(this).attr('id')))
var ajax = {
url: '/base/dataset/search_read',
async: false
};
this.rpc(ajax, {
model: this.dataset.model,
domain: self.dataset.domain,
context :self.dataset.context
}, function(response) {
ganttChartControl.clearAll();
jQuery("#GanttDiv").children().remove();
self.load_event(response);
});
},
do_search: function (domains, contexts, groupbys) {
var self = this;
return this.rpc('/base/session/eval_domain_and_context', {
domains: domains,
contexts: contexts,
group_by_seq: groupbys
}, function (results) {
self.dataset.context = results.context;
self.dataset.domain = results.domain;
return self.reload_gantt(self.dataset.domain);
});
this.rpc('/base_gantt/ganttview/reload_gantt',{
'domain':domain,
'color_field':color_field,
'model': model
},function(res) {
ganttChartControl.clearAll();
jQuery("#GanttDiv").children().remove();
self.load_event(res);
});
},
reverse_convert_date_format: function(date) {
return date.getFullYear()+"-"+(date.getMonth()+1)+"-"+date.getDate();
},
on_task_end_resize : function(task) {
this.rpc('/base_gantt/ganttview/on_event_resize',
{'id' : task.getId(),
'end_date' : this.reverse_convert_date_format(task.getFinishDate()),
'duration' : task.getDuration()
},
function(result) {
})
},
on_task_end_drag : function(task) {
this.rpc('/base_gantt/ganttview/on_event_drag',
{'id' : task.getId(),
'start_date' : this.reverse_convert_date_format(task.getEST()),
'end_date' : this.reverse_convert_date_format(task.getFinishDate()),
'duration' : task.getDuration()
},
function(result) {
})
}
});
@@ -224,5 +344,4 @@ openerp.base_gantt.GanttView = openerp.base.Controller.extend({
// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them
};
// vim:et fdc=0 fdl=0:
@@ -3,12 +3,9 @@
<h3 class="title"><t t-esc="view.fields_view.arch.attrs.string"/></h3>
<table class="gantt-view" width="100%" height="100%" cellspacing="0" cellpadding="0">
<tr>
<td style="width:85%">
<td>
<div style="width:100%;height:300px;position:relative" id="GanttDiv"/>
</td>
<td valign = "top">
<div id="gantt-sidebar"/>
</td>
</tr>
</table>
</t>
+2
View File
@@ -422,6 +422,8 @@ class HttpRequest(object):
self.applicationsession = applicationsession
self.httpsession_id = "cookieid"
self.httpsession = cherrypy.session
self.context = kw.get('context', {})
self.session = self.httpsession.setdefault(kw.get('session_id', None), OpenERPSession())
self.result = ""
print "GET/POST --> %s.%s %s %r" % (controller.__class__.__name__, f.__name__, request, kw)
r = f(controller, self, **kw)