@@ -1,40 +1,17 @@
|
||||
<templates>
|
||||
<t t-name="repetition-text-content" t-trim="both">
|
||||
<t t-foreach="seq">*</t>
|
||||
</t>
|
||||
<t t-name="repetition-dom-content" t-trim="both">
|
||||
<t t-foreach="seq"><b/></t>
|
||||
</t>
|
||||
<t t-name="repetition-self" t-trim="both">
|
||||
<b t-foreach="seq"/>
|
||||
</t>
|
||||
<t t-name="repetition-text-content" t-foreach="seq">*</t>
|
||||
<t t-name="repetition-dom-content" t-foreach="seq"><b/></t>
|
||||
<b t-name="repetition-self" t-foreach="seq"/>
|
||||
|
||||
<t t-name="scope-self" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq"/></t>
|
||||
</t>
|
||||
<t t-name="scope-value" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_value"/></t>
|
||||
</t>
|
||||
<t t-name="scope-index" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_index"/></t>
|
||||
</t>
|
||||
<t t-name="scope-first" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_first"/> </t>
|
||||
</t>
|
||||
<t t-name="scope-last" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_last"/> </t>
|
||||
</t>
|
||||
<t t-name="scope-parity" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_parity"/> </t>
|
||||
</t>
|
||||
<t t-name="scope-size" t-trim="both">
|
||||
<t t-foreach="seq"><t t-esc="seq_size"/> </t>
|
||||
</t>
|
||||
<t t-name="scope-self" t-foreach="seq" t-esc="seq"/>
|
||||
<t t-name="scope-value" t-foreach="seq" t-esc="seq_value"/>
|
||||
<t t-name="scope-index" t-foreach="seq" t-esc="seq_index"/>
|
||||
<t t-name="scope-first" t-foreach="seq"><t t-esc="seq_first"/> </t>
|
||||
<t t-name="scope-last" t-foreach="seq"><t t-esc="seq_last"/> </t>
|
||||
<t t-name="scope-parity" t-foreach="seq"><t t-esc="seq_parity"/> </t>
|
||||
<t t-name="scope-size" t-foreach="seq"><t t-esc="seq_size"/> </t>
|
||||
|
||||
<t t-name="aliasing" t-trim="both">
|
||||
<t t-foreach="seq" t-as="item"><t t-esc="item"/></t>
|
||||
</t>
|
||||
<t t-name="loopvars-aliasing" t-trim="both">
|
||||
<t t-foreach="seq" t-as="item"><t t-esc="item_parity"/> </t>
|
||||
</t>
|
||||
<t t-name="aliasing" t-foreach="seq" t-as="item" t-esc="item"/>
|
||||
<t t-name="loopvars-aliasing"
|
||||
t-foreach="seq" t-as="item"><t t-esc="item_parity"/> </t>
|
||||
</templates>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<templates>
|
||||
<t t-name="trim-content-both" t-trim="both">
|
||||
ok
|
||||
</t>
|
||||
<t t-name="trim-content-before" t-trim="left"> ok </t>
|
||||
<t t-name="trim-content-after" t-trim="right"> ok </t>
|
||||
<t t-name="trim-content-unknown" t-trim="stuff"> ok </t>
|
||||
|
||||
<t t-name="trim-children-text" t-trim="inner">o k</t>
|
||||
<t t-name="trim-children-elements" t-trim="inner">
|
||||
<foo/>
|
||||
<bar/>
|
||||
</t>
|
||||
<t t-name="trim-mixed-children" t-trim="inner">
|
||||
[ <foo/> ]
|
||||
</t>
|
||||
<t t-name='trim-children-left' t-trim="left inner"> <foo/> <bar/> </t>
|
||||
<t t-name='trim-children-right' t-trim="right inner"> <foo/> <bar/> </t>
|
||||
</templates>
|
||||
@@ -5,9 +5,10 @@
|
||||
<link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen"/>
|
||||
<script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>
|
||||
|
||||
<script type="text/javascript" src="qweb.js"></script>
|
||||
<script type="text/javascript" src="qweb2.js"></script>
|
||||
|
||||
<script>
|
||||
QWeb = new QWeb2.Engine();
|
||||
function trim(s) {
|
||||
return s.replace(/(^\s+|\s+$)/g, '');
|
||||
}
|
||||
@@ -193,39 +194,6 @@
|
||||
"Call with t-import (calls in current context)");
|
||||
});
|
||||
|
||||
module("Trim", {
|
||||
setup: function () {
|
||||
QWeb.add_template('qweb-test-trim.xml');
|
||||
},
|
||||
teardown: function () {
|
||||
QWeb.templates = [];
|
||||
QWeb.tag = {};
|
||||
QWeb.att = {};
|
||||
}
|
||||
});
|
||||
test('Trim content', function () {
|
||||
equals(QWeb.render('trim-content-both', {}), 'ok',
|
||||
"Trims before and after content");
|
||||
equals(QWeb.render('trim-content-before', {}), 'ok ',
|
||||
"Trims before content");
|
||||
equals(QWeb.render('trim-content-after', {}), ' ok',
|
||||
"Trims after content");
|
||||
raises(function () {QWeb.render('trim-content-unknown', {});},
|
||||
"Trims with an unknown mode");
|
||||
});
|
||||
test('Trim content *and* children', function () {
|
||||
equals(QWeb.render('trim-children-text', {}), 'o k',
|
||||
"Trims text children");
|
||||
equals(QWeb.render('trim-children-elements', {}), '<foo/><bar/>',
|
||||
"Full trim");
|
||||
equals(QWeb.render('trim-mixed-children', {}), '[<foo/>]',
|
||||
"Trims mixed content of text and elements");
|
||||
equals(QWeb.render('trim-children-left', {}), '<foo/><bar/> ',
|
||||
"Trims children and left of content");
|
||||
equals(QWeb.render('trim-children-right', {}), ' <foo/><bar/>',
|
||||
"Trims children and right of content");
|
||||
});
|
||||
|
||||
module("Foreach", {
|
||||
setup: function () {
|
||||
QWeb.add_template('qweb-test-foreach.xml');
|
||||
@@ -252,19 +220,19 @@
|
||||
"each value of the sequence is also via the _value");
|
||||
equals(QWeb.render('scope-index', {seq:seq}), '01234',
|
||||
"the current 0-based index is available via _index");
|
||||
equals(QWeb.render('scope-first', {seq:seq}), 'true false false false false',
|
||||
equals(QWeb.render('scope-first', {seq:seq}), 'true false false false false ',
|
||||
"_first says whether the current item is the first of the sequence");
|
||||
equals(QWeb.render('scope-last', {seq:seq}), 'false false false false true',
|
||||
equals(QWeb.render('scope-last', {seq:seq}), 'false false false false true ',
|
||||
"_last says whether the current item is the last of the sequence");
|
||||
equals(QWeb.render('scope-parity', {seq:seq}), 'even odd even odd even',
|
||||
equals(QWeb.render('scope-parity', {seq:seq}), 'even odd even odd even ',
|
||||
"the parity (odd/even) of the current row is available via _parity");
|
||||
equals(QWeb.render('scope-size', {seq:seq}), '5 5 5 5 5',
|
||||
equals(QWeb.render('scope-size', {seq:seq}), '5 5 5 5 5 ',
|
||||
"the total length of the sequence is available through _size");
|
||||
});
|
||||
test('Name aliasing via t-as', function () {
|
||||
equals(QWeb.render('aliasing', {seq:seq}), '43210',
|
||||
"the inner value can be re-bound via t-as");
|
||||
equals(QWeb.render('loopvars-aliasing', {seq:seq}), 'even odd even odd even',
|
||||
equals(QWeb.render('loopvars-aliasing', {seq:seq}), 'even odd even odd even ',
|
||||
"inner loop variables should be rebound as well");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -234,8 +234,11 @@ var QWeb = {
|
||||
return this.render(t_att["call"], d);
|
||||
},
|
||||
render_tag_js:function(e, t_att, g_att, v) {
|
||||
var dict_name = t_att["js"] || "dict";
|
||||
v[dict_name] = v;
|
||||
var r = this.eval_str(this.render_element(e, t_att, g_att, v), v);
|
||||
return t_att["js"] != "quiet" ? r : "";
|
||||
delete(v[dict_name]);
|
||||
return r || '';
|
||||
},
|
||||
/**
|
||||
* Renders a foreach loop (@t-foreach).
|
||||
|
||||
@@ -0,0 +1,562 @@
|
||||
// TODO: trim support
|
||||
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(','),
|
||||
actions_precedence: 'foreach,if,call,set,esc,escf,raw,rawf,js,debug,log'.split(','),
|
||||
word_replacement: {
|
||||
'and': '&&',
|
||||
'or': '||',
|
||||
'gt': '>',
|
||||
'gte': '>=',
|
||||
'lt': '<',
|
||||
'lte': '<='
|
||||
},
|
||||
tools: {
|
||||
exception: function(message, context) {
|
||||
context = context || {};
|
||||
var prefix = 'QWeb2';
|
||||
if (context.template) {
|
||||
prefix += " - template['" + context.template + "']";
|
||||
}
|
||||
throw prefix + ": " + message;
|
||||
},
|
||||
js_escape: function(s) {
|
||||
return "'" + s.replace(/\r?\n/g, "\\n").replace(/'/g, "\\'") + "'";
|
||||
},
|
||||
html_escape: function(s, attribute) {
|
||||
if (s === null || s === undefined) {
|
||||
return '';
|
||||
}
|
||||
s = (new String(s)).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
if (attribute) {
|
||||
s = s.replace(/"/g, '"');
|
||||
}
|
||||
return s;
|
||||
},
|
||||
gen_attribute: function(o) {
|
||||
if (o !== null && o !== undefined) {
|
||||
if (o.constructor === Array) {
|
||||
if (o[1] !== null && o[1] !== undefined) {
|
||||
return ' ' + o[0] + '="' + this.html_escape(o[1]) + '"';
|
||||
}
|
||||
} else if (typeof o === 'object') {
|
||||
var r = '';
|
||||
for (var k in o) {
|
||||
if (o.hasOwnProperty(k)) {
|
||||
r += this.gen_attribute([k, o[k]]);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
},
|
||||
extend: function(dst, src, exclude) {
|
||||
for (var p in src) {
|
||||
if (src.hasOwnProperty(p) && !(exclude && this.arrayIndexOf(exclude, p) !== -1)) {
|
||||
dst[p] = src[p];
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
arrayIndexOf : function(array, item) {
|
||||
for (var i = 0, ilen = array.length; i < ilen; i++) {
|
||||
if (array[i] === item) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
call: function(context, template, old_dict, _import, callback) {
|
||||
var new_dict = this.extend({}, old_dict);
|
||||
new_dict['__caller__'] = old_dict['__template__'];
|
||||
if (callback) {
|
||||
new_dict[0] = callback(context, new_dict);
|
||||
}
|
||||
var r = context.engine._render(template, new_dict);
|
||||
if (_import) {
|
||||
if (_import === '*') {
|
||||
this.extend(old_dict, new_dict, ['__caller__', '__template__']);
|
||||
} else {
|
||||
_import = _import.split(',');
|
||||
for (var i = 0, ilen = _import.length; i < ilen; i++) {
|
||||
var v = _import[i];
|
||||
old_dict[v] = new_dict[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
},
|
||||
foreach: function(context, enu, as, old_dict, callback) {
|
||||
if (enu != null) {
|
||||
var size, new_dict = this.extend({}, old_dict);
|
||||
new_dict[as + "_all"] = enu;
|
||||
var as_value = as + "_value",
|
||||
as_index = as + "_index",
|
||||
as_first = as + "_first",
|
||||
as_last = as + "_last",
|
||||
as_parity = as + "_parity";
|
||||
if (size = enu.length) {
|
||||
new_dict[as + "_size"] = size;
|
||||
for (var j = 0, jlen = enu.length; j < jlen; j++) {
|
||||
var cur = enu[j];
|
||||
new_dict[as_value] = cur;
|
||||
new_dict[as_index] = j;
|
||||
new_dict[as_first] = j === 0;
|
||||
new_dict[as_last] = j + 1 === size;
|
||||
new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
|
||||
if (cur.constructor === Object) {
|
||||
this.extend(new_dict, cur);
|
||||
}
|
||||
new_dict[as] = cur;
|
||||
callback(context, new_dict);
|
||||
}
|
||||
} else if (enu.constructor == Number) {
|
||||
var _enu = [];
|
||||
for (var i = 0; i < enu; i++) {
|
||||
_enu.push(i);
|
||||
}
|
||||
this.foreach(context, enu, as, old_dict, callback);
|
||||
} else {
|
||||
var index = 0;
|
||||
for (var k in enu) {
|
||||
if (enu.hasOwnProperty(k)) {
|
||||
var v = enu[k];
|
||||
new_dict[as_value] = v;
|
||||
new_dict[as_index] = index;
|
||||
new_dict[as_first] = index === 0;
|
||||
new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
|
||||
new_dict[as] = v;
|
||||
callback(context, new_dict);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.exception("No enumerator given to foreach", context);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QWeb2.Engine = (function() {
|
||||
function Engine() {
|
||||
// TODO: handle prefix at template level : t-prefix="x"
|
||||
this.prefix = 't';
|
||||
this.templates_resources = []; // TODO: implement this.reload()
|
||||
this.templates = {};
|
||||
this.compiled_templates = {};
|
||||
this.dict = {};
|
||||
this.tools = QWeb2.tools;
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
this.add_template(arguments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
QWeb2.tools.extend(Engine.prototype, {
|
||||
add_template : function(template) {
|
||||
this.templates_resources.push(template);
|
||||
if (template.constructor === String) {
|
||||
template = this.load_xml(template);
|
||||
}
|
||||
var ec = (template.documentElement && template.documentElement.childNodes) || template.childNodes || [];
|
||||
for (var i = 0; i < ec.length; i++) {
|
||||
var node = ec[i];
|
||||
if (node.nodeType === 1) {
|
||||
if (node.nodeName == 'parsererror') {
|
||||
return this.tools.exception(node.innerText);
|
||||
}
|
||||
var name = node.getAttribute(this.prefix + '-name');
|
||||
if (name) {
|
||||
this.templates[name] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
load_xml : function(s) {
|
||||
s = s.replace(/^\s*|\s*$/g, '');
|
||||
if (s[0] === '<?xml') {
|
||||
return this.load_xml_string(s);
|
||||
} else {
|
||||
var req = this.get_xhr();
|
||||
if (req) {
|
||||
// TODO: third parameter is async : https://developer.mozilla.org/en/XMLHttpRequest#open()
|
||||
// do an on_ready in QWeb2{} that could be passed to add_template
|
||||
req.open('GET', s, false);
|
||||
req.send(null);
|
||||
if (req.responseXML) {
|
||||
if (req.responseXML.documentElement.nodeName == "parsererror") {
|
||||
return this.tools.exception(req.responseXML.documentElement.childNodes[0].nodeValue);
|
||||
}
|
||||
return req.responseXML;
|
||||
} else {
|
||||
return this.load_xml_string(req.responseText);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
load_xml_string : function(s) {
|
||||
if (window.DOMParser) {
|
||||
var dp = new DOMParser();
|
||||
var r = dp.parseFromString(s, "text/xml");
|
||||
if (r.body && r.body.firstChild && r.body.firstChild.nodeName == 'parsererror') {
|
||||
return this.tools.exception(r.body.innerText);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
var xDoc;
|
||||
try {
|
||||
// new ActiveXObject("Msxml2.DOMDocument.4.0");
|
||||
xDoc = new ActiveXObject("MSXML2.DOMDocument");
|
||||
} catch (e) {
|
||||
return this.tools.exception("Could not find a DOM Parser");
|
||||
}
|
||||
xDoc.async = false;
|
||||
xDoc.preserveWhiteSpace = true;
|
||||
xDoc.loadXML(s);
|
||||
return xDoc;
|
||||
},
|
||||
get_xhr : function() {
|
||||
if (window.XMLHttpRequest) {
|
||||
return new window.XMLHttpRequest();
|
||||
}
|
||||
try {
|
||||
return new ActiveXObject('MSXML2.XMLHTTP.3.0');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
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" +
|
||||
" var context = { engine : this, template : " + (this.tools.js_escape(template)) + " };\n" +
|
||||
" dict = dict || {};\n" +
|
||||
" dict['__template__'] = '" + template + "';\n" +
|
||||
" var r = [];\n" +
|
||||
" /* START TEMPLATE */ try {\n" +
|
||||
(e.compile()) + "\n" +
|
||||
" /* END OF TEMPLATE */ } catch(error) {\n" +
|
||||
" context.engine.tools.exception('Runtime Error: ' + error, context);\n" +
|
||||
" }\n" +
|
||||
" return r.join('');\n" +
|
||||
"});";
|
||||
},
|
||||
render : function(template, dict) {
|
||||
//console.time("QWeb render template " + template);
|
||||
var r = this._render(template, dict);
|
||||
//console.timeEnd("QWeb render template " + template);
|
||||
return r;
|
||||
},
|
||||
_render : function(template, dict) {
|
||||
if (this.compiled_templates[template]) {
|
||||
return this.compiled_templates[template].apply(this, [dict || {}]);
|
||||
} else if (this.templates[template]) {
|
||||
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);
|
||||
} catch (error) {
|
||||
console.log(code);
|
||||
this.tools.exception("Error evaluating template: " + error, { template: name });
|
||||
}
|
||||
this.compiled_templates[template] = tcompiled;
|
||||
return this.render(template, dict);
|
||||
} else {
|
||||
return this.tools.exception("Template '" + template + "' not found");
|
||||
}
|
||||
}
|
||||
});
|
||||
return Engine;
|
||||
})();
|
||||
|
||||
QWeb2.Element = (function() {
|
||||
function Element(engine, node) {
|
||||
this.engine = engine;
|
||||
this.node = node;
|
||||
this.tag = node.tagName;
|
||||
this.actions = {};
|
||||
this.actions_done = [];
|
||||
this.attributes = {};
|
||||
this.children = [];
|
||||
this._top = [];
|
||||
this._bottom = [];
|
||||
this._indent = 1;
|
||||
this.process_children = true;
|
||||
var childs = this.node.childNodes;
|
||||
if (childs) {
|
||||
for (var i = 0, ilen = childs.length; i < ilen; i++) {
|
||||
this.children.push(new QWeb2.Element(this.engine, childs[i]));
|
||||
}
|
||||
}
|
||||
var attrs = this.node.attributes;
|
||||
if (attrs) {
|
||||
for (var j = 0, jlen = attrs.length; j < jlen; j++) {
|
||||
var attr = attrs[j];
|
||||
var name = attr.name;
|
||||
var m = name.match(new RegExp("^" + this.engine.prefix + "-(.+)"));
|
||||
if (m) {
|
||||
name = m[1];
|
||||
if (name === 'name') {
|
||||
continue;
|
||||
}
|
||||
this.actions[name] = attr.value;
|
||||
} else {
|
||||
this.attributes[name] = attr.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QWeb2.tools.extend(Element.prototype, {
|
||||
compile : function() {
|
||||
switch (this.node.nodeType) {
|
||||
case 3:
|
||||
case 4:
|
||||
this.top_string(this.node.data);
|
||||
break;
|
||||
case 1:
|
||||
this.compile_element();
|
||||
}
|
||||
var r = this.flatten_output(this._top);
|
||||
if (this.process_children) {
|
||||
for (var i = 0, ilen = this.children.length; i < ilen; i++) {
|
||||
var child = this.children[i];
|
||||
child._indent = this._indent;
|
||||
r += child.compile();
|
||||
}
|
||||
}
|
||||
r += this.flatten_output(this._bottom);
|
||||
return r;
|
||||
},
|
||||
format_expression : function(e) {
|
||||
/* Naive format expression builder. Replace reserved words and variables to dict[variable]
|
||||
* Does not handle spaces before dot yet, and causes problems for anonymous functions. Use t-js="" for that */
|
||||
if (e === '0') {
|
||||
return "dict['0']";
|
||||
}
|
||||
if (QWeb2.expressions_cache[e]) {
|
||||
return QWeb2.expressions_cache[e];
|
||||
}
|
||||
var chars = e.split(''),
|
||||
instring = '',
|
||||
invar = '',
|
||||
invar_pos = 0,
|
||||
r = '';
|
||||
chars.push(' ');
|
||||
for (var i = 0, ilen = chars.length; i < ilen; i++) {
|
||||
var c = chars[i];
|
||||
if (instring.length) {
|
||||
if (c === instring && chars[i - 1] !== "\\") {
|
||||
instring = '';
|
||||
}
|
||||
} else if (c === '"' || c === "'") {
|
||||
instring = c;
|
||||
} else if (c.match(/[a-zA-Z_\$]/) && !invar.length) {
|
||||
invar = c;
|
||||
invar_pos = i;
|
||||
continue;
|
||||
} else if (c.match(/\W/) && invar.length) {
|
||||
// TODO: Should check for possible spaces before dot
|
||||
if (chars[invar_pos - 1] !== '.' && QWeb2.tools.arrayIndexOf(QWeb2.reserved_words, invar) < 0) {
|
||||
invar = QWeb2.word_replacement[invar] || ("dict['" + invar + "']");
|
||||
}
|
||||
r += invar;
|
||||
invar = '';
|
||||
} else if (invar.length) {
|
||||
invar += c;
|
||||
continue;
|
||||
}
|
||||
r += c;
|
||||
}
|
||||
r = r.slice(0, -1);
|
||||
QWeb2.expressions_cache[e] = r;
|
||||
return r;
|
||||
},
|
||||
string_interpolation : function(s) {
|
||||
if (!s) {
|
||||
return "''";
|
||||
}
|
||||
var regex = /^{(.*)}(.*)/,
|
||||
src = s.split(/#/),
|
||||
r = [];
|
||||
for (var i = 0, ilen = src.length; i < ilen; i++) {
|
||||
var val = src[i],
|
||||
m = val.match(regex);
|
||||
if (m) {
|
||||
r.push("(" + this.format_expression(m[1]) + ")");
|
||||
if (m[2]) {
|
||||
r.push(this.engine.tools.js_escape(m[2]));
|
||||
}
|
||||
} else if (!(i === 0 && val === '')) {
|
||||
r.push(this.engine.tools.js_escape((i === 0 ? '' : '#') + val));
|
||||
}
|
||||
}
|
||||
return r.join(' + ');
|
||||
},
|
||||
indent : function() {
|
||||
return this._indent++;
|
||||
},
|
||||
dedent : function() {
|
||||
if (this._indent !== 0) {
|
||||
return this._indent--;
|
||||
}
|
||||
},
|
||||
get_indent : function() {
|
||||
return new Array(this._indent + 1).join("\t");
|
||||
},
|
||||
flatten_output : function(out) {
|
||||
var r = '';
|
||||
for (var i = 0, ilen = out.length; i < ilen; i++) {
|
||||
var val = out[i];
|
||||
if (val.constructor === Array) {
|
||||
r += val[0] + "r.push(" + this.engine.tools.js_escape(val[1]) + ");\n";
|
||||
} else {
|
||||
r += val;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
},
|
||||
top : function(s) {
|
||||
return this._top.push(this.get_indent() + s + '\n');
|
||||
},
|
||||
top_string : function(s) {
|
||||
var top = this._top;
|
||||
if (top.length && top[top.length - 1].constructor === Array) {
|
||||
return top[top.length - 1][1] += s;
|
||||
} else {
|
||||
return top.push([this.get_indent(), s]);
|
||||
}
|
||||
},
|
||||
bottom : function(s) {
|
||||
return this._bottom.unshift(this.get_indent() + s + '\n');
|
||||
},
|
||||
bottom_string : function(s) {
|
||||
var bottom = this._bottom;
|
||||
if (bottom.length && bottom[0].constructor === Array) {
|
||||
return bottom[0][1] += s;
|
||||
} else {
|
||||
return bottom.unshift([this.get_indent(), s]);
|
||||
}
|
||||
},
|
||||
compile_element : function() {
|
||||
for (var i = 0, ilen = QWeb2.actions_precedence.length; i < ilen; i++) {
|
||||
var a = QWeb2.actions_precedence[i];
|
||||
if (a in this.actions) {
|
||||
var value = this.actions[a];
|
||||
var key = 'compile_action_' + a;
|
||||
if (!this[key]) {
|
||||
this.engine.tools.exception("No handler method for action '" + a + "'");
|
||||
}
|
||||
this[key](value);
|
||||
}
|
||||
}
|
||||
if (this.tag !== this.engine.prefix) {
|
||||
var tag = "<" + this.tag;
|
||||
for (var a in this.attributes) {
|
||||
tag += this.engine.tools.gen_attribute([a, this.attributes[a]]);
|
||||
}
|
||||
this.top_string(tag);
|
||||
if (this.actions.att) {
|
||||
this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(this.actions.att)) + "));");
|
||||
}
|
||||
for (var a in this.actions) {
|
||||
var v = this.actions[a];
|
||||
var m = a.match(/att-(.+)/);
|
||||
if (m) {
|
||||
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.format_expression(v)) + ")]));");
|
||||
}
|
||||
var m = a.match(/attf-(.+)/);
|
||||
if (m) {
|
||||
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.string_interpolation(v)) + ")]));");
|
||||
}
|
||||
}
|
||||
if (this.children.length || this.actions.opentag === 'true') {
|
||||
this.top_string(">");
|
||||
this.bottom_string("</" + this.tag + ">");
|
||||
} else {
|
||||
this.top_string("/>");
|
||||
}
|
||||
}
|
||||
},
|
||||
compile_action_if : function(value) {
|
||||
this.top("if (" + (this.format_expression(value)) + ") {");
|
||||
this.bottom("}");
|
||||
this.indent();
|
||||
},
|
||||
compile_action_foreach : function(value) {
|
||||
var as = this.actions['as'] || value.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
//TODO: exception if t-as not valid
|
||||
this.top("context.engine.tools.foreach(context, " + (this.format_expression(value)) + ", " + (this.engine.tools.js_escape(as)) + ", dict, function(context, dict) {");
|
||||
this.bottom("});");
|
||||
this.indent();
|
||||
},
|
||||
compile_action_call : function(value) {
|
||||
var _import = this.actions['import'] || '';
|
||||
if (this.children.length === 0) {
|
||||
return this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + "));");
|
||||
} else {
|
||||
this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + ", function(context, dict) {");
|
||||
this.bottom("}));");
|
||||
this.indent();
|
||||
this.top("var r = [];");
|
||||
return this.bottom("return r.join('');");
|
||||
}
|
||||
},
|
||||
compile_action_set : function(value) {
|
||||
if (this.actions['value']) {
|
||||
this.top("dict['" + value + "'] = (" + (this.format_expression(this.actions['value'])) + ");");
|
||||
this.process_children = false;
|
||||
} else {
|
||||
if (this.children.length === 0) {
|
||||
this.top("dict['" + value + "'] = '';");
|
||||
} else if (this.children.length === 1 && this.children[0].node.nodeType === 3) {
|
||||
this.top("dict['" + value + "'] = " + (this.engine.tools.js_escape(this.children[0].node.data)) + ";");
|
||||
this.process_children = false;
|
||||
} else {
|
||||
this.top("dict['" + value + "'] = (function(dict) {");
|
||||
this.bottom("})(dict);");
|
||||
this.indent();
|
||||
this.top("var r = [];");
|
||||
this.bottom("return r.join('');");
|
||||
}
|
||||
}
|
||||
},
|
||||
compile_action_esc : function(value) {
|
||||
this.top("r.push(context.engine.tools.html_escape(" + (this.format_expression(value)) + "));");
|
||||
},
|
||||
compile_action_escf : function(value) {
|
||||
this.top("r.push(context.engine.tools.html_escape(" + (this.string_interpolation(value)) + "));");
|
||||
},
|
||||
compile_action_raw : function(value) {
|
||||
this.top("r.push(" + (this.format_expression(value)) + ");");
|
||||
},
|
||||
compile_action_rawf : function(value) {
|
||||
this.top("r.push(" + (this.string_interpolation(value)) + ");");
|
||||
},
|
||||
compile_action_js : function(value) {
|
||||
this.top("(function(" + value + ") {");
|
||||
this.bottom("})(dict);");
|
||||
this.indent();
|
||||
if (this.children.length === 1) {
|
||||
var lines = this.children[0].node.data.split(/\r?\n/);
|
||||
for (var i = 0, ilen = lines.length; i < ilen; i++) {
|
||||
this.top(lines[i]);
|
||||
}
|
||||
} else {
|
||||
this.engine.tools.exception("'js' code block contains " + this.children.length + " nodes instead of 1");
|
||||
}
|
||||
// Maybe I could handle the children ?
|
||||
this.process_children = false;
|
||||
},
|
||||
compile_action_debug : function(value) {
|
||||
this.top("debugger;");
|
||||
},
|
||||
compile_action_log : function(value) {
|
||||
this.top("console.log(" + this.format_expression(value) + "});");
|
||||
}
|
||||
});
|
||||
return Element;
|
||||
})();
|
||||
@@ -8,7 +8,10 @@
|
||||
<script type="text/javascript" src="/base/static/lib/LABjs/LAB.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/underscore/underscore.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/underscore/underscore.strings.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/qweb/qweb2.js"></script>
|
||||
<!-- Uncomment in order to use legacy QWeb
|
||||
<script type="text/javascript" src="/base/static/lib/qweb/qweb.js"></script>
|
||||
-->
|
||||
<script type="text/javascript" src="/base/static/lib/jquery/jquery-1.5.2.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js"></script>
|
||||
<script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-timepicker-addon.js"></script>
|
||||
@@ -35,6 +38,7 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
QWeb = window.QWeb || new QWeb2.Engine();
|
||||
var oe = openerp.init();
|
||||
oe.base.webclient("oe");
|
||||
});
|
||||
|
||||
@@ -530,34 +530,19 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
|
||||
}
|
||||
},
|
||||
on_confirmed: function() {
|
||||
var attrs = this.node.attrs;
|
||||
if (attrs.special) {
|
||||
this.on_button_action({
|
||||
result : { type: 'ir.actions.act_window_close' }
|
||||
var self = this;
|
||||
|
||||
this.execute_action(
|
||||
this.node.attrs, this.view.dataset, this.session.action_manager,
|
||||
this.view.datarecord.id, function (result) {
|
||||
self.log("Button returned", result);
|
||||
self.view.reload();
|
||||
});
|
||||
} else {
|
||||
var type = attrs.type || 'workflow';
|
||||
var context = _.extend({}, this.view.dataset.context, attrs.context || {});
|
||||
switch(type) {
|
||||
case 'object':
|
||||
return this.view.dataset.call(attrs.name, [this.view.datarecord.id], [context], this.on_button_action);
|
||||
case 'action':
|
||||
return this.rpc('/base/action/load', { action_id: parseInt(attrs.name) }, this.on_button_action);
|
||||
default:
|
||||
return this.view.dataset.exec_workflow(this.view.datarecord.id, attrs.name, this.on_button_action);
|
||||
}
|
||||
}
|
||||
},
|
||||
on_button_action: function(r) {
|
||||
console.log("Got reesonse button", r)
|
||||
if (r.result && r.result.constructor == Object) {
|
||||
this.session.action_manager.do_action(r.result);
|
||||
} else {
|
||||
this.log("Button returned", r.result);
|
||||
this.view.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
// let WidgetButton execute actions
|
||||
_.extend(openerp.base.form.WidgetButton.prototype,
|
||||
openerp.base.ActionExecutor);
|
||||
|
||||
openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({
|
||||
init: function(view, node) {
|
||||
@@ -969,7 +954,9 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
|
||||
},
|
||||
do_add_record: function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
// TODO: need to open a popup with search view
|
||||
debugger;
|
||||
var pop = new openerp.base.form.Many2XSelectPopup(null, this.m2m_field.view.session);
|
||||
pop.select_element(null,null);
|
||||
},
|
||||
on_select_row: function(event) {
|
||||
var $target = $(event.currentTarget);
|
||||
@@ -991,6 +978,20 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({
|
||||
identifier_prefix: "many2xselectpopup",
|
||||
template: "Many2XSelectPopup",
|
||||
select_element: function(model, callback) {
|
||||
this.model = model;
|
||||
this.callback = callback;
|
||||
var html = this.render();
|
||||
jQuery(html).dialog({title: '',
|
||||
modal: true,
|
||||
minWidth: 800});
|
||||
this.start();
|
||||
}
|
||||
});
|
||||
|
||||
openerp.base.form.FieldReference = openerp.base.form.Field.extend({
|
||||
init: function(view, node) {
|
||||
this._super(view, node);
|
||||
|
||||
@@ -40,6 +40,8 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
||||
* @param {null|String} [options.addable="New"] should the new-record button be displayed, and what should its label be. Use ``null`` to hide the button.
|
||||
* @param {Boolean} [options.sortable=true] is it possible to sort the table by clicking on column headers
|
||||
* @param {Boolean} [options.reorderable=true] is it possible to reorder list rows
|
||||
*
|
||||
* @borrows openerp.base.ActionExecutor#execute_action as #execute_action
|
||||
*/
|
||||
init: function(view_manager, session, element_id, dataset, view_id, options) {
|
||||
this._super(session, element_id);
|
||||
@@ -145,20 +147,20 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
||||
});
|
||||
$table.delegate(
|
||||
'td.oe-field-cell button', 'click', function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
var $cell = $(e.currentTarget).closest('td');
|
||||
|
||||
var col_index = $cell.prevAll('td').length;
|
||||
var field = self.visible_columns[col_index];
|
||||
var action = field.name;
|
||||
|
||||
var $row = $cell.parent('tr');
|
||||
var row = self.rows[$row.prevAll().length];
|
||||
|
||||
var context = _.extend(
|
||||
{}, self.dataset.context, field.context || {});
|
||||
self.notification.notify(
|
||||
'[Dummy] Execute action', self.dataset.model + '#' + action + "([" + row.data.id.value + '])');
|
||||
// TODO: implement actions correctly once the corresponding manager has been extracted (and filled)
|
||||
e.stopImmediatePropagation();
|
||||
// TODO: we should probably only reload content, also maybe diff records or something, instead of replacing every single row
|
||||
self.execute_action(
|
||||
field, self.dataset, self.session.action_manager,
|
||||
row.data.id.value, self.do_reload);
|
||||
});
|
||||
$table.delegate(
|
||||
'td.oe-record-delete button', 'click', this.do_delete);
|
||||
@@ -393,6 +395,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
|
||||
// TODO: implement sort (click on column headers), if sorted, the list can not be reordered anymore
|
||||
// TODO: implement reorder (drag and drop rows)
|
||||
});
|
||||
_.extend(openerp.base.ListView.prototype, openerp.base.ActionExecutor);
|
||||
|
||||
openerp.base.TreeView = openerp.base.Controller.extend({
|
||||
});
|
||||
|
||||
@@ -17,6 +17,8 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
||||
/**
|
||||
* Process an action
|
||||
* Supported actions: act_window
|
||||
*
|
||||
* If the action contains a 'no_sidebar' key, the resulting view will never contain a sidebar.
|
||||
*/
|
||||
do_action: function(action) {
|
||||
var self = this;
|
||||
@@ -42,7 +44,11 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
||||
if (this.viewmanager) {
|
||||
this.viewmanager.stop();
|
||||
}
|
||||
this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action, true);
|
||||
var sidebar = true;
|
||||
if(action.no_sidebar) {
|
||||
sidebar = false;
|
||||
}
|
||||
this.viewmanager = new openerp.base.ViewManagerAction(this.session, this.element_id, action, sidebar);
|
||||
this.viewmanager.start();
|
||||
}
|
||||
break;
|
||||
@@ -57,6 +63,58 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Mixin for action-executing objects, provides handling of OpenERP actions to
|
||||
* all clients.
|
||||
*
|
||||
* Mix into existing classes via ``_.extend`` of the class's prototype.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
openerp.base.ActionExecutor =
|
||||
/**
|
||||
* @lends openerp.base.ActionExecutor#
|
||||
*/ {
|
||||
/**
|
||||
* Fetches and executes the action identified by ``action_data``.
|
||||
*
|
||||
* @param {Object} action_data the action descriptor data
|
||||
* @param {String} action_data.name the action name, used to uniquely identify the action to find and execute it
|
||||
* @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``)
|
||||
* @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
|
||||
* @param {Object} [action_data.context=null] additional action context, to add to the current context
|
||||
* @param {openerp.base.DataSet} dataset a dataset object used to communicate with the server
|
||||
* @param {openerp.base.ActionManager} action_manager object able to actually execute the action, if any is fetched
|
||||
* @param {Number} [record_id] the identifier of the object on which the action is to be applied
|
||||
* @param {Function} on_no_action callback to execute if the action does not generate any result (no new action)
|
||||
*/
|
||||
execute_action: function (action_data, dataset, action_manager, record_id, on_no_action) {
|
||||
var handler = function (r) {
|
||||
if (r.result && r.result.constructor == Object) {
|
||||
action_manager.do_action(r.result);
|
||||
} else {
|
||||
on_no_action(r.result);
|
||||
}
|
||||
};
|
||||
|
||||
if (action_data.special) {
|
||||
handler({
|
||||
result : { type: 'ir.actions.act_window_close' }
|
||||
});
|
||||
} else {
|
||||
var context = _.extend({}, dataset.context, action_data.context || {});
|
||||
switch(action_data.type) {
|
||||
case 'object':
|
||||
return dataset.call(action_data.name, [record_id], [context], handler);
|
||||
case 'action':
|
||||
return this.rpc('/base/action/load', { action_id: parseInt(action_data.name, 10) }, handler);
|
||||
default:
|
||||
return dataset.exec_workflow(record_id, action_data.name, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Registry for all the main views
|
||||
*/
|
||||
|
||||
@@ -534,8 +534,11 @@
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="ViewManager.sidebar.internal">
|
||||
<t t-set="the_condition" t-value="sections.length > 0 && _.detect(sections, function(x)
|
||||
{return x.elements.length > 0;}) != undefined"/>
|
||||
<t t-js="d">
|
||||
d.the_condition = d.sections.length > 0 && d._.detect(d.sections, function(x) {
|
||||
return x.elements.length > 0;
|
||||
}) != undefined
|
||||
</t>
|
||||
<t t-if="the_condition">
|
||||
<a class="toggle-sidebar"></a>
|
||||
<div t-att-id="element_id" class="sidebar-sub-div">
|
||||
@@ -566,4 +569,9 @@
|
||||
</p>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="Many2XSelectPopup">
|
||||
<div id="element_id">
|
||||
Brian is in the kitchen.
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
// TODO when the tests will not be broken anymore
|
||||
@@ -7,6 +7,7 @@ $(document).ready(function () {
|
||||
// views loader stuff
|
||||
window.openerp.base.data(openerp);
|
||||
window.openerp.base.views(openerp);
|
||||
window.openerp.base.list(openerp);
|
||||
window.openerp.base.form(openerp);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,8 +26,8 @@ $(document).ready(function () {
|
||||
// views loader stuff
|
||||
window.openerp.base.data(openerp);
|
||||
window.openerp.base.views(openerp);
|
||||
window.openerp.base.form(openerp);
|
||||
window.openerp.base.list(openerp);
|
||||
window.openerp.base.form(openerp);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import controllers
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Base Dashboard",
|
||||
"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'],
|
||||
'active': True
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import main
|
||||
@@ -0,0 +1,17 @@
|
||||
from base.controllers.main import View, clean_action
|
||||
import openerpweb
|
||||
|
||||
class Dashboard(View):
|
||||
_cp_path = "/base_dashboard/dashboard"
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def load(self, req, node_attrs):
|
||||
|
||||
action_id = int(node_attrs['name'])
|
||||
actions = req.session.model('ir.actions.actions')
|
||||
result = actions.read([action_id],['type'], req.session.context)
|
||||
if not result:
|
||||
raise _('Action not found!')
|
||||
action = req.session.model(result[0]['type']).read([action_id], False, req.session.context)[0]
|
||||
clean_action(action, req.session)
|
||||
return {'action': action}
|
||||
@@ -0,0 +1 @@
|
||||
content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4
|
||||
@@ -0,0 +1,261 @@
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,936 @@
|
||||
/*
|
||||
* 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,85 @@
|
||||
<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>
|
||||
@@ -0,0 +1,104 @@
|
||||
openerp.base_dashboard = function(openerp){
|
||||
|
||||
QWeb.add_template('/base_dashboard/static/src/xml/base_dashboard.xml');
|
||||
|
||||
openerp.base.form.Board = openerp.base.form.Widget.extend({
|
||||
init: function(view, node) {
|
||||
|
||||
this._super(view, node);
|
||||
this.template = "Board";
|
||||
},
|
||||
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() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.rpc('/base_dashboard/dashboard/load',{
|
||||
node_attrs: this.node.attrs
|
||||
},
|
||||
this.on_load_action);
|
||||
},
|
||||
|
||||
on_load_action: function(result) {
|
||||
var action = result.action;
|
||||
|
||||
action.search_view = false;
|
||||
action.no_sidebar = true;
|
||||
action.search_view_id = 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);
|
||||
action_manager.start();
|
||||
this.board.getWidget(node_attrs.name).url = action_manager.do_action(action);
|
||||
}
|
||||
})
|
||||
|
||||
openerp.base.form.Vpaned = 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;
|
||||
},
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
var children = this.node.children;
|
||||
for(var chld=0; chld<children.length; chld++) {
|
||||
var ch_widget = children[chld].children;
|
||||
for(var ch=0; ch<ch_widget.length; ch++) {
|
||||
var widget_type = ch_widget[ch].tag;
|
||||
var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, ch_widget[ch], this.board, this.child_index);
|
||||
widget.start();
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
openerp.base.form.widgets.add('hpaned', 'openerp.base.form.Board');
|
||||
openerp.base.form.widgets.add('vpaned', 'openerp.base.form.Vpaned');
|
||||
openerp.base.form.widgets.add('action', 'openerp.base.form.Action');
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<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>
|
||||
</t>
|
||||
</template>
|
||||
@@ -16,7 +16,6 @@ import cherrypy.lib.static
|
||||
import simplejson
|
||||
|
||||
import nonliterals
|
||||
import xmlrpctimeout
|
||||
import logging
|
||||
|
||||
#-----------------------------------------------------------
|
||||
@@ -93,7 +92,7 @@ class OpenERPSession(object):
|
||||
self.client_timezone = False
|
||||
|
||||
def proxy(self, service):
|
||||
s = xmlrpctimeout.TimeoutServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service), timeout=5)
|
||||
s = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service))
|
||||
return s
|
||||
|
||||
def bind(self, db, uid, password):
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import xmlrpclib
|
||||
import httplib
|
||||
|
||||
class TimeoutHTTPConnection(httplib.HTTPConnection):
|
||||
def connect(self):
|
||||
httplib.HTTPConnection.connect(self)
|
||||
self.sock.settimeout(self.timeout)
|
||||
|
||||
class TimeoutHTTP(httplib.HTTP):
|
||||
_connection_class = TimeoutHTTPConnection
|
||||
def set_timeout(self, timeout):
|
||||
self._conn.timeout = timeout
|
||||
|
||||
class TimeoutTransport(xmlrpclib.Transport):
|
||||
def __init__(self, timeout=10, *l, **kw):
|
||||
xmlrpclib.Transport.__init__(self,*l,**kw)
|
||||
self.timeout=timeout
|
||||
def make_connection(self, host):
|
||||
conn = TimeoutHTTP(host)
|
||||
conn.set_timeout(self.timeout)
|
||||
return conn
|
||||
|
||||
class TimeoutServerProxy(xmlrpclib.ServerProxy):
|
||||
def __init__(self,uri,timeout=10,*l,**kw):
|
||||
kw['transport']=TimeoutTransport(timeout=timeout, use_datetime=kw.get('use_datetime',0))
|
||||
xmlrpclib.ServerProxy.__init__(self,uri,*l,**kw)
|
||||
|
||||
if __name__ == "__main__":
|
||||
s=TimeoutServerProxy('http://127.0.0.1:9090',timeout=2)
|
||||
s.dummy()
|
||||
Reference in New Issue
Block a user