Add Gruntfile and fix jshint

This commit is contained in:
C?dric Krier 2013-01-04 21:29:48 +01:00
parent 70ce8b5556
commit 6fae77fb6d
17 changed files with 3285 additions and 3108 deletions

63
Gruntfile.js Normal file
View file

@ -0,0 +1,63 @@
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concat: {
dist: {
src: [
'js/sao.js',
'js/rpc.js',
'js/pyson.js',
'js/session.js',
'js/model.js',
'js/tab.js',
'js/screen.js',
'js/view.js',
'js/action.js',
'js/common.js'
],
dest: 'dist/<%= pkg.name %>.js'
}
},
jshint: {
dist: {
options: {
jshintrc: 'js/.jshintrc'
},
src: ['dist/<%= pkg.name %>.js']
},
grunt: {
src: ['Gruntfile.js']
},
tests: {
options: {
jshintrc: 'tests/.jshintrc'
},
src: ['tests/*.js']
}
},
uglify: {
options: {
banner: '/*! <%= pkg.name %>-<%= pkg.version %> | GPL-3\n' +
'This file is part of Tryton. ' +
'The COPYRIGHT file at the top level of\n' +
'this repository contains the full copyright notices ' +
'and license terms. */\n'
},
dist: {
src: 'dist/<%= pkg.name %>.js',
dest: 'dist/<%= pkg.name %>.min.js'
}
}
});
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
// Default task(s).
grunt.registerTask('default', ['concat', 'jshint', 'uglify']);
};

View file

@ -20,16 +20,12 @@ this repository contains the full copyright notices and license terms. -->
document.write(unescape('%3CLINK rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" media="screen"/%3E'));
}
</SCRIPT>
<SCRIPT type="text/javascript" src="js/class.js"></SCRIPT>
<SCRIPT type="text/javascript" src="js/sao.js"></SCRIPT>
<SCRIPT type="text/javascript" src="js/rpc.js"></SCRIPT>
<SCRIPT type="text/javascript" src="js/session.js"></SCRIPT>
<SCRIPT type="text/javascript" src="js/model.js"></SCRIPT>
<SCRIPT type="text/javascript" src="js/tab.js"></SCRIPT>
<SCRIPT type="text/javascript" src="js/screen.js"></SCRIPT>
<SCRIPT type="text/javascript" src="js/view.js"></SCRIPT>
<SCRIPT type="text/javascript" src="js/action.js"></SCRIPT>
<SCRIPT type="text/javascript" src="js/common.js"></SCRIPT>
<SCRIPT type="text/javascript" src="dist/sao.min.js"></SCRIPT>
<SCRIPT type="text/javascript">
if (typeof Sao == 'undefined') {
document.write(unescape('%3CSCRIPT type="text/javascript" src="dist/sao.js"%3E%3C/SCRIPT%3E'));
}
</SCRIPT>
</HEAD>
<BODY>
<DIV id="main">

8
js/.jshintrc Normal file
View file

@ -0,0 +1,8 @@
{
"browser": true,
"devel": true,
"jquery": true,
"predef": [
"Sao"
]
}

View file

@ -1,85 +1,89 @@
/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
'use strict';
(function() {
'use strict';
Sao.Action = {};
Sao.Action = {};
Sao.Action.exec_action = function(action, data, context) {
if (context == undefined) {
context = {};
}
if (data == undefined) {
data = {};
} else {
data = jQuery.extend({}, data);
}
switch (action['type']) {
case 'ir.action.act_window':
var params = {};
params.view_ids = false;
params.view_mode = null;
if (!jQuery.isEmptyObject(action.views)) {
params.view_ids = [];
params.view_mode = [];
action.views.forEach(function(x) {
params.view_ids.push(x[0]);
params.view_mode.push(x[1]);
});
} else if (!jQuery.isEmptyObject(action.view_id)) {
params.view_ids = [action.view_id[0]];
}
// TODO context, domain, search, tab_domain
params.name = false;
if (action.window_name) {
params.name = action.name;
}
params.model = action.res_model || data.res_model;
params.res_id = action.res_id || data.res_id;
Sao.Tab.create(params);
return;
case 'ir.action.wizard':
return;
case 'ir.action.report':
return;
case 'ir.action.url':
window.open(action['url'], '_blank');
return;
}
};
Sao.Action.exec_keyword = function(keyword, data, context, warning, alwaysask)
{
if (warning == undefined) {
warning = true;
}
if (alwaysask == undefined) {
alwaysask = false;
}
var actions = [];
var model_id = data['id'];
var args = {
'method': 'model.' + 'ir.action.keyword.get_keyword',
'params': [keyword, [data['model'], model_id], {}]
};
var prm = Sao.rpc(args, Sao.Session.current_session);
var exec_action = function(actions) {
var keyact = {};
for (var i in actions) {
var action = actions[i];
keyact[action['name'].replace(/_/g, '')] = action;
Sao.Action.exec_action = function(action, data, context) {
if (context === undefined) {
context = {};
}
if (data === undefined) {
data = {};
} else {
data = jQuery.extend({}, data);
}
switch (action.type) {
case 'ir.action.act_window':
var params = {};
params.view_ids = false;
params.view_mode = null;
if (!jQuery.isEmptyObject(action.views)) {
params.view_ids = [];
params.view_mode = [];
action.views.forEach(function(x) {
params.view_ids.push(x[0]);
params.view_mode.push(x[1]);
});
} else if (!jQuery.isEmptyObject(action.view_id)) {
params.view_ids = [action.view_id[0]];
}
// TODO context, domain, search, tab_domain
params.name = false;
if (action.window_name) {
params.name = action.name;
}
params.model = action.res_model || data.res_model;
params.res_id = action.res_id || data.res_id;
Sao.Tab.create(params);
return;
case 'ir.action.wizard':
return;
case 'ir.action.report':
return;
case 'ir.action.url':
window.open(action.url, '_blank');
return;
}
// TODO translation
var prm = Sao.common.selection('Select your action', keyact, alwaysask);
prm.done(function(action) {
Sao.Action.exec_action(action, data, context);
});
prm.fail(function() {
if (jQuery.isEmptyObject(keyact) && warning) {
// TODO translation
alert('No action defined!');
}
});
return prm;
};
return prm.pipe(exec_action);
};
Sao.Action.exec_keyword = function(keyword, data, context, warning,
alwaysask)
{
if (warning === undefined) {
warning = true;
}
if (alwaysask === undefined) {
alwaysask = false;
}
var actions = [];
var model_id = data.id;
var args = {
'method': 'model.' + 'ir.action.keyword.get_keyword',
'params': [keyword, [data.model, model_id], {}]
};
var prm = Sao.rpc(args, Sao.Session.current_session);
var exec_action = function(actions) {
var keyact = {};
for (var i in actions) {
var action = actions[i];
keyact[action.name.replace(/_/g, '')] = action;
}
// TODO translation
var prm = Sao.common.selection('Select your action', keyact,
alwaysask);
prm.done(function(action) {
Sao.Action.exec_action(action, data, context);
});
prm.fail(function() {
if (jQuery.isEmptyObject(keyact) && warning) {
// TODO translation
alert('No action defined!');
}
});
return prm;
};
return prm.pipe(exec_action);
};
}());

View file

@ -1,24 +0,0 @@
/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
'use strict';
var Class = function(Parent, props) {
var ClassConstructor = function() {
if (!(this instanceof ClassConstructor))
throw Error('Constructor function requires new operator');
if (this.init) {
this.init.apply(this, arguments);
}
};
// Plug prototype chain
ClassConstructor.prototype = Object.create(Parent.prototype);
ClassConstructor._super = Parent.prototype;
if (props) {
for (var name in props) {
ClassConstructor.prototype[name] = props[name];
}
}
return ClassConstructor;
};

View file

@ -1,18 +1,20 @@
/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
'use strict';
(function() {
'use strict';
Sao.common = {};
Sao.common = {};
Sao.common.selection = function(title, values, alwaysask) {
if (alwaysask == undefined) {
alwaysask = false;
}
if ((Object.keys(values).length == 1) && (!alwaysask)) {
var prm = jQuery.Deferred();
var key = Object.keys(values)[0];
prm.resolve(values[key]);
return prm;
}
// TODO
};
Sao.common.selection = function(title, values, alwaysask) {
if (alwaysask === undefined) {
alwaysask = false;
}
if ((Object.keys(values).length == 1) && (!alwaysask)) {
var prm = jQuery.Deferred();
var key = Object.keys(values)[0];
prm.resolve(values[key]);
return prm;
}
// TODO
};
}());

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

292
js/rpc.js
View file

@ -1,150 +1,152 @@
/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
'use strict';
(function() {
'use strict';
Sao.rpc = function(args, session) {
var dfd = jQuery.Deferred();
if (!session) {
session = new Sao.Session();
}
Sao.rpc = function(args, session) {
var dfd = jQuery.Deferred();
if (!session) {
session = new Sao.Session();
}
var ajax_prm = jQuery.ajax({
'contentType': 'application/json',
'data': JSON.stringify(Sao.rpc.prepareObject({
'method': args.method,
'params': [session.user_id, session.session].concat(args.params)
})),
'dataType': 'json',
'url': '/' + (session.database || ''),
'type': 'post'
var ajax_prm = jQuery.ajax({
'contentType': 'application/json',
'data': JSON.stringify(Sao.rpc.prepareObject({
'method': args.method,
'params': [session.user_id, session.session].concat(args.params)
})),
'dataType': 'json',
'url': '/' + (session.database || ''),
'type': 'post'
});
var ajax_success = function(data) {
if (data === null) {
Sao.warning('Unable to reach the server');
dfd.reject();
} else if (data.error) {
if (data.error[0] == 'UserWarning') {
} else if (data.error[0] == 'UserError') {
// TODO
} else if (data.error[0] == 'ConcurrencyException') {
// TODO
} else if (data.error[0] == 'NotLogged') {
//Try to relog
var cred_prm = jQuery.Deferred();
Sao.Session.renew_credentials(session, cred_prm);
cred_prm.done(function() {
Sao.rpc(session, args, dfd);
});
cred_prm.fail(dfd.reject);
} else {
console.log('ERROR');
Sao.error(data.error[0], data.error[1]);
}
dfd.reject();
} else {
dfd.resolve(data.result);
}
};
var ajax_error = function() {
console.log('ERROR');
dfd.reject();
};
ajax_prm.success(ajax_success);
ajax_prm.error(ajax_error);
return dfd.promise();
};
Sao.rpc.convertJSONObject = function(value, index, parent) {
if (value instanceof Array) {
for (var i = 0, length = value.length; i < length; i++) {
Sao.rpc.convertJSONObject(value[i], i, value);
}
} else if ((typeof(value) != 'string') &&
(typeof(value) != 'number')) {
if (value && value.__class__) {
switch (value.__class__) {
case 'datetime':
value = new Date(Date.UTC(value.year,
value.month, value.day, value.hour,
value.minute, value.second));
break;
case 'date':
value = new Date(value.year,
value.month, value.day);
break;
case 'time':
throw new Error('Time support not implemented');
case 'buffer':
throw new Error('Buffer support not implemented');
case 'Decimal':
value = new Number(value.decimal);
break;
}
if (parent) {
parent[index] = value;
}
} else {
for (var p in value) {
Sao.rpc.convertJSONObject(value[p], p, value);
}
}
}
return parent || value;
};
Sao.rpc.prepareObject = function(value, index, parent) {
if (value instanceof Array) {
for (var i = 0, length = value.length; i < length; i++) {
Sao.rpc.prepareObject(value[i], i, value);
}
} else if ((typeof(value) != 'string') && (typeof(value) != 'number')) {
if (value instanceof Date) {
if (value.getHours() || value.getMinutes() || value.getSeconds)
{
value = {
'__class__': 'datetime',
'year': value.getUTCFullYear(),
'month': value.getUTCMonth(),
'day': value.getUTCDate(),
'hour': value.getUTCHours(),
'minute': value.getUTCMinutes(),
'second': value.getUTCSeconds()
};
} else {
value = {
'__class__': 'date',
'year': value.getFullYear(),
'month': value.getMonth(),
'day': value.getDate()
};
}
if (parent) {
parent[index] = value;
}
} else if (value instanceof Number) {
value = {
'__class__': 'Decimal',
'decimal': value.valueOf()
};
if (parent) {
parent[index] = value;
}
} else {
for (var p in value) {
Sao.rpc.prepareObject(value[p], p, value);
}
}
}
return parent || value;
};
jQuery.ajaxSetup({
converters: {
'text json': function(json) {
return Sao.rpc.convertJSONObject(jQuery.parseJSON(json));
}
}
});
var ajax_success = function(data) {
if (data === null) {
Sao.warning('Unable to reach the server');
dfd.reject();
} else if (data.error) {
if (data.error[0] == 'UserWarning') {
} else if (data.error[0] == 'UserError') {
// TODO
} else if (data.error[0] == 'ConcurrencyException') {
// TODO
} else if (data.error[0] == 'NotLogged') {
//Try to relog
var cred_prm = jQuery.Deferred();
Sao.Session.renew_credentials(session, cred_prm);
cred_prm.done(function() {
Sao.rpc(session, args, dfd);
});
cred_prm.fail(dfd.reject);
} else {
console.log('ERROR');
Sao.error(data.error[0], data.error[1]);
}
dfd.reject();
} else {
dfd.resolve(data.result);
}
};
var ajax_error = function() {
console.log('ERROR');
dfd.reject();
};
ajax_prm.success(ajax_success);
ajax_prm.error(ajax_error);
return dfd.promise();
};
Sao.rpc.convertJSONObject = function(value, index, parent) {
if (value instanceof Array) {
for (var i = 0, length = value.length; i < length; i++) {
Sao.rpc.convertJSONObject(value[i], i, value);
}
} else if ((typeof(value) != 'string') &&
(typeof(value) != 'number')) {
if (value && value.__class__) {
switch (value.__class__) {
case 'datetime':
value = new Date(Date.UTC(value.year,
value.month, value.day, value.hour,
value.minute, value.second));
break;
case 'date':
value = new Date(value.year,
value.month, value.day);
break;
case 'time':
throw Error('Time support not implemented');
case 'buffer':
throw Error('Buffer support not implemented');
case 'Decimal':
value = new Number(value.decimal);
break;
}
if (parent) {
parent[index] = value;
}
} else {
for (var p in value) {
Sao.rpc.convertJSONObject(value[p], p, value);
}
}
}
return parent || value;
};
Sao.rpc.prepareObject = function(value, index, parent) {
if (value instanceof Array) {
for (var i = 0, length = value.length; i < length; i++) {
Sao.rpc.prepareObject(value[i], i, value);
}
} else if ((typeof(value) != 'string') && (typeof(value) != 'number')) {
if (value instanceof Date) {
if (value.getHours() || value.getMinutes() || value.getSeconds)
{
value = {
'__class__': 'datetime',
'year': value.getUTCFullYear(),
'month': value.getUTCMonth(),
'day': value.getUTCDate(),
'hour': value.getUTCHours(),
'minute': value.getUTCMinutes(),
'second': value.getUTCSeconds()
};
} else {
value = {
'__class__': 'date',
'year': value.getFullYear(),
'month': value.getMonth(),
'day': value.getDate()
};
}
if (parent) {
parent[index] = value;
}
} else if (value instanceof Number) {
value = {
'__class__': 'Decimal',
'decimal': value.valueOf()
};
if (parent) {
parent[index] = value;
}
} else {
for (var p in value) {
Sao.rpc.prepareObject(value[p], p, value);
}
}
}
return parent || value;
};
jQuery.ajaxSetup({
converters: {
'text json': function(json) {
return Sao.rpc.convertJSONObject(jQuery.parseJSON(json));
}
}
});
}());

View file

@ -1,13 +1,35 @@
/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
'use strict';
var Sao = {};
Sao.error = function(title, message) {
alert(title + '\n' + (message || ''));
};
(function() {
'use strict';
Sao.warning = function(title, message) {
alert(title + '\n' + (message || ''));
};
Sao.error = function(title, message) {
alert(title + '\n' + (message || ''));
};
Sao.warning = function(title, message) {
alert(title + '\n' + (message || ''));
};
Sao.class_ = function(Parent, props) {
var ClassConstructor = function() {
if (!(this instanceof ClassConstructor))
throw new Error('Constructor function requires new operator');
if (this.init) {
this.init.apply(this, arguments);
}
};
// Plug prototype chain
ClassConstructor.prototype = Object.create(Parent.prototype);
ClassConstructor._super = Parent.prototype;
if (props) {
for (var name in props) {
ClassConstructor.prototype[name] = props[name];
}
}
return ClassConstructor;
};
}());

View file

@ -1,139 +1,141 @@
/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
'use strict';
(function() {
'use strict';
Sao.Screen = Class(Object, {
init: function(model_name, attributes) {
this.model_name = model_name;
this.model = new Sao.Model(model_name, attributes);
this.attributes = jQuery.extend({}, attributes);
this.view_ids = jQuery.extend([], attributes.view_ids);
this.view_to_load = jQuery.extend([],
attributes.mode || ['tree', 'form']);
this.views = [];
this.current_view = null;
this.current_record = null;
this.context = attributes.context || {};
if (!attributes.row_activate) {
this.row_activate = this.default_row_activate;
} else {
this.row_activate = attributes.row_activate;
}
this.el = $('<div/>', {
'class': 'screen'
});
},
load_next_view: function() {
if (this.view_to_load) {
var view_id;
if (this.view_ids) {
view_id = this.view_ids.shift();
}
var view_type = this.view_to_load.shift();
return this.add_view_id(view_id, view_type);
}
return jQuery.when();
},
add_view_id: function(view_id, view_type) {
// TODO preload
var prm = this.model.execute('fields_view_get',
[view_id, view_type], this.context);
prm.done(this.add_view.bind(this));
return prm;
},
add_view: function(view) {
var arch = view.arch;
var fields = view.fields;
var xml_view = jQuery(jQuery.parseXML(arch));
// TODO loading lazy/eager
var loading = 'eager';
if (xml_view.children().prop('tagName') == 'form') {
loading = 'lazy';
}
for (var field in fields) {
if (!(field in this.model.fields) || loading == 'eager') {
fields[field]['loading'] = loading;
Sao.Screen = Sao.class_(Object, {
init: function(model_name, attributes) {
this.model_name = model_name;
this.model = new Sao.Model(model_name, attributes);
this.attributes = jQuery.extend({}, attributes);
this.view_ids = jQuery.extend([], attributes.view_ids);
this.view_to_load = jQuery.extend([],
attributes.mode || ['tree', 'form']);
this.views = [];
this.current_view = null;
this.current_record = null;
this.context = attributes.context || {};
if (!attributes.row_activate) {
this.row_activate = this.default_row_activate;
} else {
fields[field]['loading'] = this.model.fields[field]
.description.loading;
this.row_activate = attributes.row_activate;
}
}
this.model.add_fields(fields);
var view = Sao.View.parse(this, xml_view, view.field_childs);
this.views.push(view);
return view;
},
number_of_views: function() {
return this.views.length + this.view_to_load.length;
},
switch_view: function(view_type) {
// TODO check validity
var self = this;
if ((!view_type) || (!this.current_view) ||
(this.current_view.view_type != view_type)) {
for (var i = 0; i < this.number_of_views(); i++) {
if (this.view_to_load.length) {
return this.load_next_view().done(function() {
self.current_view = self.views.slice(-1);
return self.switch_view(view_type);
});
this.el = jQuery('<div/>', {
'class': 'screen'
});
},
load_next_view: function() {
if (this.view_to_load) {
var view_id;
if (this.view_ids) {
view_id = this.view_ids.shift();
}
this.current_view = this.views[
(this.views.indexOf(this.current_view) + 1) %
this.views.length];
if (!view_type) {
break;
} else if (this.current_view.view_type == view_type) {
break;
var view_type = this.view_to_load.shift();
return this.add_view_id(view_id, view_type);
}
return jQuery.when();
},
add_view_id: function(view_id, view_type) {
// TODO preload
var prm = this.model.execute('fields_view_get',
[view_id, view_type], this.context);
prm.done(this.add_view.bind(this));
return prm;
},
add_view: function(view) {
var arch = view.arch;
var fields = view.fields;
var xml_view = jQuery(jQuery.parseXML(arch));
// TODO loading lazy/eager
var loading = 'eager';
if (xml_view.children().prop('tagName') == 'form') {
loading = 'lazy';
}
for (var field in fields) {
if (!(field in this.model.fields) || loading == 'eager') {
fields[field].loading = loading;
} else {
fields[field].loading = this.model.fields[field]
.description.loading;
}
}
}
this.el.remove();
this.el.append(this.current_view.el);
// TODO display and cursor
return jQuery.when();
},
search_filter: function() {
var domain = [];
// TODO domain parser
this.model.add_fields(fields);
var view_widget = Sao.View.parse(this, xml_view, view.field_childs);
this.views.push(view_widget);
return view_widget;
},
number_of_views: function() {
return this.views.length + this.view_to_load.length;
},
switch_view: function(view_type) {
// TODO check validity
var self = this;
if ((!view_type) || (!this.current_view) ||
(this.current_view.view_type != view_type)) {
var switch_current_view = function() {
self.current_view = self.views.slice(-1);
return self.switch_view(view_type);
};
for (var i = 0; i < this.number_of_views(); i++) {
if (this.view_to_load.length) {
return this.load_next_view().done(switch_current_view);
}
this.current_view = this.views[
(this.views.indexOf(this.current_view) + 1) %
this.views.length];
if (!view_type) {
break;
} else if (this.current_view.view_type == view_type) {
break;
}
}
}
this.el.remove();
this.el.append(this.current_view.el);
// TODO display and cursor
return jQuery.when();
},
search_filter: function() {
var domain = [];
// TODO domain parser
if (domain.length && this.attributes.domain) {
domain.unshift('AND');
domain.push(this.attributes.domain);
} else
domain = this.attributes.domain || [];
var grp_prm = this.model.find(domain, this.attributes.offset,
this.attributes.limit, this.attributes.order,
this.context);
var group_setter = function(group) {
this.group = group;
};
grp_prm.done(group_setter.bind(this));
return grp_prm;
},
display: function() {
if (this.views) {
for (var i = 0; i < this.views.length; i++)
if (this.views[i])
this.views[i].display();
if (domain.length && this.attributes.domain) {
domain.unshift('AND');
domain.push(this.attributes.domain);
} else
domain = this.attributes.domain || [];
var grp_prm = this.model.find(domain, this.attributes.offset,
this.attributes.limit, this.attributes.order,
this.context);
var group_setter = function(group) {
this.group = group;
};
grp_prm.done(group_setter.bind(this));
return grp_prm;
},
display: function() {
if (this.views) {
for (var i = 0; i < this.views.length; i++)
if (this.views[i])
this.views[i].display();
}
},
default_row_activate: function() {
if ((this.current_view.view_type == 'tree') &&
this.current_view.keyword_open) {
Sao.Action.exec_keyword('tree_open', {
'model': this.model_name,
'id': this.get_id(),
'ids': [this.get_id()]
}, jQuery.extend({}, this.context));
} else {
this.switch_view('form');
}
},
get_id: function() {
if (this.current_record) {
return this.current_record.id;
}
}
},
default_row_activate: function() {
if ((this.current_view.view_type == 'tree') &&
this.current_view.keyword_open) {
Sao.Action.exec_keyword('tree_open', {
'model': this.model_name,
'id': this.get_id(),
'ids': [this.get_id()]
}, jQuery.extend({}, this.context));
} else {
this.switch_view('form');
}
},
get_id: function() {
if (this.current_record) {
return this.current_record.id;
}
}
});
});
}());

View file

@ -1,220 +1,222 @@
/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
'use strict';
(function() {
'use strict';
Sao.Session = Class(Object, {
init: function(database, login) {
this.database = database;
this.login = login;
this.user_id = null;
this.session = null;
if (!Sao.Session.current_session) {
Sao.Session.current_session = this;
Sao.Session = Sao.class_(Object, {
init: function(database, login) {
this.database = database;
this.login = login;
this.user_id = null;
this.session = null;
if (!Sao.Session.current_session) {
Sao.Session.current_session = this;
}
},
do_login: function(login, password) {
var dfd = jQuery.Deferred();
var args = {
'method': 'common.db.login',
'params': [login, password]
};
var ajax_prm = jQuery.ajax({
'contentType': 'application/json',
'data': JSON.stringify(args),
'dataType': 'json',
'url': '/' + this.database,
'type': 'post'
});
var ajax_success = function(data) {
if (data === null) {
Sao.warning('Unable to reach the server');
dfd.reject();
} else if (data.error) {
console.log('ERROR');
Sao.error(data.error[0], data.error[1]);
dfd.reject();
} else {
if (!data.result) {
this.user_id = null;
this.session = null;
} else {
this.user_id = data.result[0];
this.session = data.result[1];
}
dfd.resolve();
}
};
ajax_prm.success(ajax_success.bind(this));
ajax_prm.error(dfd.reject);
return dfd.promise();
},
do_logout: function() {
if (!(this.user_id && this.session)) {
return;
}
var args = {
'method': 'common.db.logout',
'params': []
};
var prm = Sao.rpc(this, args);
this.database = null;
this.login = null;
this.user_id = null;
this.session = null;
return prm;
}
},
do_login: function(login, password) {
var dfd = jQuery.Deferred();
var args = {
'method': 'common.db.login',
'params': [login, password]
});
Sao.Session.get_credentials = function(parent_dfd) {
var login; // TODO use cookie
var database = window.location.hash.replace(
/^(#(!|))/, '') || null;
var database_div, database_select;
var login_div, login_input, password_input;
var ok_func = function() {
var login_val = login_input.val();
var password_val = password_input.val();
var database_val = (database ||
database_select.val());
if (!(login_val && password_val)) {
return;
}
var session = new Sao.Session(database_val,
login_val);
var prm = session.do_login(login_val, password_val);
prm.done(function() {
parent_dfd.resolve(session);
});
login_div.dialog('close');
};
var ajax_prm = jQuery.ajax({
'contentType': 'application/json',
'data': JSON.stringify(args),
'dataType': 'json',
'url': '/' + this.database,
'type': 'post'
var keydown = function(ev) {
if (ev.which === 13)
ok_func();
};
var fill_database = function() {
jQuery.when(Sao.DB.list()).then(function(databases) {
databases.forEach(function(database) {
database_select.append(jQuery('<option/>', {
'value': database,
'text': database
}));
});
});
};
login_div = jQuery('<div/>', {
'class': 'login'
});
if (!database) {
login_div.append(jQuery('<label/>', {
'text': 'Database:' // TODO translation
}));
database_select = jQuery('<select/>');
login_div.append(database_select);
fill_database();
login_div.append(jQuery('<br/>'));
}
login_div.append(jQuery('<label/>', {
'text': 'Login:' // TODO translation
}));
login_input = jQuery('<input/>', {
'type': 'input',
'id': 'login',
'val': login
});
login_input.keydown(keydown);
login_div.append(login_input);
login_div.append(jQuery('<br/>'));
login_div.append(jQuery('<label/>', {
'text': 'Password:'
}));
password_input = jQuery('<input/>', {
'type': 'password',
'id': 'password'
});
password_input.keydown(keydown);
login_div.append(password_input);
login_div.append(jQuery('<br/>'));
login_div.dialog({
'title': 'Login', // TODO translation
'modal': true,
'buttons': {
'Cancel': function() {
jQuery(this).dialog('close');
},
'OK': ok_func
},
'open': function() {
if (login) {
password_input.focus();
} else {
login_input.focus();
}
}
});
var ajax_success = function(data) {
if (data === null) {
Sao.warning('Unable to reach the server');
dfd.reject();
} else if (data.error) {
console.log('ERROR');
Sao.error(data.error[0], data.error[1]);
dfd.reject();
} else {
if (!data.result) {
this.user_id = null;
this.session = null;
} else {
this.user_id = data.result[0];
this.session = data.result[1];
}
dfd.resolve();
}
};
Sao.Session.renew_credentials = function(session, parent_dfd) {
var login_div, password_input;
var ok_func = function() {
var password_val = password_input.val();
var prm = session.do_login(session.login, password_val);
prm.done(function() {
parent_dfd.resolve();
});
login_div.dialog('close');
};
ajax_prm.success(ajax_success.bind(this));
ajax_prm.error(dfd.reject);
return dfd.promise();
},
do_logout: function() {
if (!(this.user_id && this.session)) {
return;
}
var keydown = function(ev) {
if (ev.which === 13)
ok_func();
};
login_div = jQuery('<div/>', {
'class': 'login'
});
login_div.append(jQuery('<label/>', {
'text': 'Password:'
}));
password_input = jQuery('<input/>', {
'type': 'password',
'id': 'password'
});
password_input.keydown(keydown);
login_div.append(password_input);
login_div.append(jQuery('<br/>'));
login_div.dialog({
'title': 'Login', // TODO translation
'modal': true,
'buttons': {
'Cancel': function() {
jQuery(this).dialog('close');
},
'OK': ok_func
},
'open': function() {
password_input.focus();
}
});
};
Sao.Session.current_session = null;
Sao.DB = {};
Sao.DB.list = function() {
var args = {
'method': 'common.db.logout',
'method': 'common.db.list',
'params': []
};
var prm = Sao.rpc(this, args);
this.database = null;
this.login = null;
this.user_id = null;
this.session = null;
return prm;
}
});
Sao.Session.get_credentials = function(parent_dfd) {
var login; // TODO use cookie
var database = window.location.hash.replace(
/^(#(!|))/, '') || null;
var database_div, database_select;
var login_div, login_input, password_input;
var ok_func = function() {
var login_val = login_input.val();
var password_val = password_input.val();
var database_val = (database ||
database_select.val());
if (!(login_val && password_val)) {
return;
}
var session = new Sao.Session(database_val,
login_val);
var prm = session.do_login(login_val, password_val);
prm.done(function() {
parent_dfd.resolve(session);
});
login_div.dialog('close');
return Sao.rpc(args);
};
var keydown = function(ev) {
if (ev.which === 13)
ok_func();
};
var fill_database = function() {
$.when(Sao.DB.list()).then(function(databases) {
databases.forEach(function(database) {
database_select.append($('<option/>', {
'value': database,
'text': database
}));
});
});
};
login_div = $('<div/>', {
'class': 'login'
});
if (!database) {
login_div.append($('<label/>', {
'text': 'Database:' // TODO translation
}));
database_select = $('<select/>');
login_div.append(database_select);
fill_database();
login_div.append($('<br/>'));
}
login_div.append($('<label/>', {
'text': 'Login:' // TODO translation
}));
login_input = $('<input/>', {
'type': 'input',
'id': 'login',
'val': login
});
login_input.keydown(keydown);
login_div.append(login_input);
login_div.append($('<br/>'));
login_div.append($('<label/>', {
'text': 'Password:'
}));
password_input = $('<input/>', {
'type': 'password',
'id': 'password'
});
password_input.keydown(keydown);
login_div.append(password_input);
login_div.append($('<br/>'));
login_div.dialog({
'title': 'Login', // TODO translation
'modal': true,
'buttons': {
'Cancel': function() {
$(this).dialog('close');
},
'OK': ok_func
},
'open': function() {
if (login) {
password_input.focus();
} else {
login_input.focus();
}
}
});
};
Sao.Session.renew_credentials = function(session, parent_dfd) {
var login_div, password_input;
var ok_func = function() {
var password_val = password_input.val();
var prm = session.do_login(session.login, password_val);
prm.done(function() {
parent_dfd.resolve();
});
login_div.dialog('close');
};
var keydown = function(ev) {
if (ev.which === 13)
ok_func();
};
login_div = $('<div/>', {
'class': 'login'
});
login_div.append($('<label/>', {
'text': 'Password:'
}));
password_input = $('<input/>', {
'type': 'password',
'id': 'password'
});
password_input.keydown(keydown);
login_div.append(password_input);
login_div.append($('<br/>'));
login_div.dialog({
'title': 'Login', // TODO translation
'modal': true,
'buttons': {
'Cancel': function() {
$(this).dialog('close');
},
'OK': ok_func
},
'open': function() {
password_input.focus();
}
});
};
Sao.Session.current_session = null;
Sao.DB = {};
Sao.DB.list = function() {
var args = {
'method': 'common.db.list',
'params': []
};
return Sao.rpc(args);
};
}());

View file

@ -1,50 +1,52 @@
/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
'use strict';
(function() {
'use strict';
Sao.Tab = Class(Object, {
init: function() {
}
});
Sao.Tab = Sao.class_(Object, {
init: function() {
}
});
Sao.Tab.counter = 0;
Sao.Tab.counter = 0;
Sao.Tab.create = function(attributes) {
if (attributes.context == undefined) {
attributes.context = {};
}
var tab;
if (attributes.model) {
tab = new Sao.Tab.Form(attributes.model, attributes);
} else {
tab = new Sao.Tab.Board(attributes);
}
jQuery('#tabs').tabs();
tab.id = '#tab-' + Sao.Tab.counter++;
jQuery('#tabs').tabs('add', tab.id, tab.name);
jQuery(tab.id).html(tab.el);
jQuery('#tabs').tabs('select', tab.id);
};
Sao.Tab.create = function(attributes) {
if (attributes.context === undefined) {
attributes.context = {};
}
var tab;
if (attributes.model) {
tab = new Sao.Tab.Form(attributes.model, attributes);
} else {
tab = new Sao.Tab.Board(attributes);
}
jQuery('#tabs').tabs();
tab.id = '#tab-' + Sao.Tab.counter++;
jQuery('#tabs').tabs('add', tab.id, tab.name);
jQuery(tab.id).html(tab.el);
jQuery('#tabs').tabs('select', tab.id);
};
Sao.Tab.Form = Class(Sao.Tab, {
init: function(model_name, attributes) {
Sao.Tab.Form._super.init.call(this);
var screen = new Sao.Screen(model_name, attributes);
this.screen = screen;
this.attributes = jQuery.extend({}, attributes);
this.name = attributes.name; // XXX use screen current view title
var el = $('<div/>', {
'class': 'form'
});
this.el = el;
this.screen.load_next_view().pipe(function() {
return screen.switch_view();
}).done(function() {
el.html(screen.el);
}).done(function() {
screen.search_filter().done(function() {
screen.display();
Sao.Tab.Form = Sao.class_(Sao.Tab, {
init: function(model_name, attributes) {
Sao.Tab.Form._super.init.call(this);
var screen = new Sao.Screen(model_name, attributes);
this.screen = screen;
this.attributes = jQuery.extend({}, attributes);
this.name = attributes.name; // XXX use screen current view title
var el = jQuery('<div/>', {
'class': 'form'
});
});
}
});
this.el = el;
this.screen.load_next_view().pipe(function() {
return screen.switch_view();
}).done(function() {
el.html(screen.el);
}).done(function() {
screen.search_filter().done(function() {
screen.display();
});
});
}
});
}());

View file

@ -1,279 +1,287 @@
/* This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. */
'use strict';
(function() {
'use strict';
Sao.View = Class(Object, {
init: function(screen, xml) {
this.screen = screen;
this.view_type = null;
this.el = null;
}
});
Sao.View = Sao.class_(Object, {
init: function(screen, xml) {
this.screen = screen;
this.view_type = null;
this.el = null;
}
});
Sao.View.parse = function(screen, xml, children_field) {
switch (xml.children().prop('tagName')) {
case 'tree':
return new Sao.View.Tree(screen, xml, children_field);
}
};
Sao.View.parse = function(screen, xml, children_field) {
switch (xml.children().prop('tagName')) {
case 'tree':
return new Sao.View.Tree(screen, xml, children_field);
case 'form':
return new Sao.View.Form(screen, xml, children_field);
}
};
Sao.View.tree_column_get = function(type) {
switch (type) {
default:
return Sao.View.Tree.CharColumn;
}
};
Sao.View.tree_column_get = function(type) {
return Sao.View.Tree.CharColumn;
};
Sao.View.Tree = Class(Sao.View, {
init: function(screen, xml, children_field) {
Sao.View.Tree._super.init.call(this, screen, xml);
this.view_type = 'tree';
this.el = $('<div/>', {
'class': 'treeview'
});
this.expanded = {};
this.children_field = children_field;
this.keyword_open = xml.children()[0].getAttribute('keyword_open');
// Columns
this.columns = [];
this.create_columns(screen.model, xml);
// Table of records
this.table = $('<table/>', {
'class': 'tree'
});
this.el.append(this.table);
var thead = $('<thead/>');
this.table.append(thead);
var tr = $('<tr/>');
thead.append(tr);
this.columns.forEach(function(column) {
var th = $('<th/>', {
'text': column.attributes['string']
Sao.View.Tree = Sao.class_(Sao.View, {
init: function(screen, xml, children_field) {
Sao.View.Tree._super.init.call(this, screen, xml);
this.view_type = 'tree';
this.el = jQuery('<div/>', {
'class': 'treeview'
});
tr.append(th);
});
this.tbody = $('<tbody/>');
this.table.append(this.tbody);
this.expanded = {};
this.children_field = children_field;
this.keyword_open = xml.children()[0].getAttribute('keyword_open');
// Footer with pagination stuff
var footer = $('<div/>', {
'class': 'treefooter'
});
this.previous_button = $('<button/>').button({
'disabled': true,
'icons': {
primary: 'ui-icon-triangle-1-w'
},
'text': false,
'label': 'Previous' //TODO translation
});
footer.append(this.previous_button);
this.pagination = $('<span/>', {
text: '0 / 0'
});
footer.append(this.pagination);
this.next_button = $('<button/>').button({
'disabled': true,
'icons': {
primary: 'ui-icon-triangle-1-e'
},
'text': false,
'label': 'Next' //TODO translation
});
this.pagination.append(this.next_button);
this.el.append(footer);
},
create_columns: function(model, xml) {
var self = this;
xml.find('tree').children().each(function(pos, child) {
if (child.tagName == 'field') {
var name = child.getAttribute('name');
var attributes = {
'name': child.getAttribute('name'),
'readonly': child.getAttribute('readonly') == 1,
'widget': child.getAttribute('widget'),
'tree_invisible': child.getAttribute('tree_invisible') == 1,
'expand': child.getAttribute('expand') == 1,
'icon': child.getAttribute('icon'),
'sum': child.getAttribute('sum'),
'width': child.getAttribute('width'),
'orientation': child.getAttribute('orientation'),
'float_time': child.getAttribute('float_time'),
'pre_validate': child.getAttribute('pre_validate') == 1,
'completion': child.getAttribute('completion') == 1
};
var attribute_names = ['relation', 'domain', 'selection',
'relation_field', 'string', 'views', 'invisible',
'add_remove', 'sort', 'context', 'filename'];
for (var i in attribute_names) {
var attr = attribute_names[i];
if ((attr in model.fields[name].description) &&
(child.getAttribute(attr) == null)) {
attributes[attr] = model.fields[name].description[attr];
// Columns
this.columns = [];
this.create_columns(screen.model, xml);
// Table of records
this.table = jQuery('<table/>', {
'class': 'tree'
});
this.el.append(this.table);
var thead = jQuery('<thead/>');
this.table.append(thead);
var tr = jQuery('<tr/>');
thead.append(tr);
this.columns.forEach(function(column) {
var th = jQuery('<th/>', {
'text': column.attributes.string
});
tr.append(th);
});
this.tbody = jQuery('<tbody/>');
this.table.append(this.tbody);
// Footer with pagination stuff
var footer = jQuery('<div/>', {
'class': 'treefooter'
});
this.previous_button = jQuery('<button/>').button({
'disabled': true,
'icons': {
primary: 'ui-icon-triangle-1-w'
},
'text': false,
'label': 'Previous' //TODO translation
});
footer.append(this.previous_button);
this.pagination = jQuery('<span/>', {
text: '0 / 0'
});
footer.append(this.pagination);
this.next_button = jQuery('<button/>').button({
'disabled': true,
'icons': {
primary: 'ui-icon-triangle-1-e'
},
'text': false,
'label': 'Next' //TODO translation
});
this.pagination.append(this.next_button);
this.el.append(footer);
},
create_columns: function(model, xml) {
var self = this;
xml.find('tree').children().each(function(pos, child) {
var attributes, column;
if (child.tagName == 'field') {
var name = child.getAttribute('name');
attributes = {
'name': child.getAttribute('name'),
'readonly': child.getAttribute('readonly') == 1,
'widget': child.getAttribute('widget'),
'tree_invisible': child.getAttribute(
'tree_invisible') == 1,
'expand': child.getAttribute('expand') == 1,
'icon': child.getAttribute('icon'),
'sum': child.getAttribute('sum'),
'width': child.getAttribute('width'),
'orientation': child.getAttribute('orientation'),
'float_time': child.getAttribute('float_time'),
'pre_validate': child.getAttribute('pre_validate') == 1,
'completion': child.getAttribute('completion') == 1
};
var attribute_names = ['relation', 'domain', 'selection',
'relation_field', 'string', 'views', 'invisible',
'add_remove', 'sort', 'context', 'filename'];
for (var i in attribute_names) {
var attr = attribute_names[i];
if ((attr in model.fields[name].description) &&
(child.getAttribute(attr) === null)) {
attributes[attr] = model.fields[name]
.description[attr];
}
}
var ColumnFactory = Sao.View.tree_column_get(
attributes.widget);
column = new ColumnFactory(model, attributes);
} else if (child.tagName == 'button') {
attributes = {
'help': child.getAttribute('help'),
'string': child.getAttribute('string'),
'confirm': child.getAttribute('confirm'),
'name': child.getAttribute('name')
};
column = new Sao.View.Tree.ButtonColumn(attributes);
}
var ColumnFactory = Sao.View.tree_column_get(
attributes['widget']);
var column = new ColumnFactory(model, attributes);
} else if (child.tagName == 'button') {
var attributes = {
'help': child.getAttribute('help'),
'string': child.getAttribute('string'),
'confirm': child.getAttribute('confirm'),
'name': child.getAttribute('name')
};
var column = new Sao.View.Tree.ButtonColumn(attributes);
}
self.columns.push(column);
});
},
display: function() {
this.tbody.empty();
var add_row = function(record, pos, group) {
var tree_row = new Sao.View.Tree.Row(this, record, pos);
tree_row.display();
};
this.screen.group.forEach(add_row.bind(this));
},
switch_: function(path) {
this.screen.row_activate();
},
select_changed: function(record) {
this.screen.current_record = record;
// TODO validate if editable
// TODO update_children
}
});
Sao.View.Tree.Row = Class(Object, {
init: function(tree, record, pos, parent) {
this.tree = tree;
this.record = record;
this.children_field = tree.children_field;
this.expander = null;
this.expander_icon = null;
if (parent) {
var path = jQuery.extend([], parent.path.split('.'));
} else
var path = [];
path.push(pos);
this.path = path.join('.');
this.el = $('<tr/>');
this.el.click(this.select_row.bind(this));
var switch_ = function() {
this.el.addClass('ui-state-highlight');
this.tree.select_changed(this.record);
this.tree.switch_(path);
};
this.el.dblclick(switch_.bind(this));
},
is_expanded: function() {
return (this.path in this.tree.expanded);
},
display: function() {
console.log(this.path);
var depth = this.path.split('.').length;
for (var i = 0; i < this.tree.columns.length; i++) {
var td = $('<td/>');
if ((i == 0) && this.children_field) {
if (this.is_expanded()) {
var expanded = 'ui-icon-minus';
} else
var expanded = 'ui-icon-plus';
this.expander = $('<span/>', {
'class': 'expander'
});
this.expander.html('&nbsp;');
// 16 == minimum width of icon
this.expander.css('width', ((depth * 10) + 16) + 'px');
this.expander.css('float', 'left');
this.expander.click(this.toggle_row.bind(this));
this.expander_icon = $('<i/>', {
'class': 'ui-icon ' + expanded
});
this.expander.append(this.expander_icon);
this.expander_icon.css('float', 'right');
td.append(this.expander);
var update_expander = function() {
if (jQuery.isEmptyObject(
this.record.field_get(this.children_field))) {
this.expander_icon.hide();
}
};
this.record.load(this.children_field).done(
update_expander.bind(this));
}
var column = this.tree.columns[i];
td.append(column.render(this.record));
this.el.append(td);
}
this.tree.tbody.append(this.el);
if (this.is_expanded()) {
var add_children = function() {
var add_row = function(record, pos, group) {
var tree_row = new Sao.View.Tree.Row(this.tree, record,
pos, this);
tree_row.display();
};
var children = this.record.field_get_client(children_field);
children.forEach(add_row.bind(this));
self.columns.push(column);
});
},
display: function() {
this.tbody.empty();
var add_row = function(record, pos, group) {
var tree_row = new Sao.View.Tree.Row(this, record, pos);
tree_row.display();
};
var children_field = this.children_field;
console.log('Load children of ' + this.path);
this.record.load(this.children_field).done(add_children.bind(this));
this.screen.group.forEach(add_row.bind(this));
},
switch_: function(path) {
this.screen.row_activate();
},
select_changed: function(record) {
this.screen.current_record = record;
// TODO validate if editable
// TODO update_children
}
},
toggle_row: function() {
if (this.is_expanded()) {
this.expander_icon.removeClass('ui-icon-minus');
this.expander_icon.addClass('ui-icon-plus');
delete this.tree.expanded[this.path];
} else {
this.expander_icon.removeClass('ui-icon-plus');
this.expander_icon.addClass('ui-icon-minus');
this.tree.expanded[this.path] = this;
}
this.tree.display();
},
select_row: function() {
this.el.toggleClass('ui-state-highlight');
if (this.el.hasClass('ui-state-highlight')) {
this.tree.select_changed(this.record);
} else {
this.tree.select_changed(null);
}
}
});
});
Sao.View.Tree.CharColumn = Class(Object, {
init: function(model, attributes) {
this.type = 'field';
this.field = model.fields[attributes['name']];
this.attributes = attributes;
},
render: function(record) {
var cell = $('<span/>');
var update_text = function() {
cell.text(this.field.get_client(record));
};
record.load(this.attributes.name).done(update_text.bind(this));
return cell;
}
});
Sao.View.Tree.Row = Sao.class_(Object, {
init: function(tree, record, pos, parent) {
this.tree = tree;
this.record = record;
this.children_field = tree.children_field;
this.expander = null;
this.expander_icon = null;
var path = [];
if (parent) {
path = jQuery.extend([], parent.path.split('.'));
}
path.push(pos);
this.path = path.join('.');
this.el = jQuery('<tr/>');
this.el.click(this.select_row.bind(this));
var switch_ = function() {
this.el.addClass('ui-state-highlight');
this.tree.select_changed(this.record);
this.tree.switch_(path);
};
this.el.dblclick(switch_.bind(this));
},
is_expanded: function() {
return (this.path in this.tree.expanded);
},
display: function() {
console.log(this.path);
var depth = this.path.split('.').length;
var update_expander = function() {
if (jQuery.isEmptyObject(
this.record.field_get(
this.children_field))) {
Sao.View.Tree.ButtonColumn = Class(Object, {
init: function(attributes) {
this.type = 'button';
this.attributes = attributes;
},
render: function() {
var button = $('<button/>', {
'class': 'button',
'label': this.attributes.string
});
return button;
}
});
this.expander_icon.hide();
}
};
for (var i = 0; i < this.tree.columns.length; i++) {
var td = jQuery('<td/>');
if ((i === 0) && this.children_field) {
var expanded = 'ui-icon-plus';
if (this.is_expanded()) {
expanded = 'ui-icon-minus';
}
this.expander = jQuery('<span/>', {
'class': 'expander'
});
this.expander.html('&nbsp;');
// 16 == minimum width of icon
this.expander.css('width', ((depth * 10) + 16) + 'px');
this.expander.css('float', 'left');
this.expander.click(this.toggle_row.bind(this));
this.expander_icon = jQuery('<i/>', {
'class': 'ui-icon ' + expanded
});
this.expander.append(this.expander_icon);
this.expander_icon.css('float', 'right');
td.append(this.expander);
this.record.load(this.children_field).done(
update_expander.bind(this));
}
var column = this.tree.columns[i];
td.append(column.render(this.record));
this.el.append(td);
}
this.tree.tbody.append(this.el);
if (this.is_expanded()) {
var add_children = function() {
var add_row = function(record, pos, group) {
var tree_row = new Sao.View.Tree.Row(this.tree, record,
pos, this);
tree_row.display();
};
var children = this.record.field_get_client(children_field);
children.forEach(add_row.bind(this));
};
var children_field = this.children_field;
console.log('Load children of ' + this.path);
this.record.load(this.children_field).done(
add_children.bind(this));
}
},
toggle_row: function() {
if (this.is_expanded()) {
this.expander_icon.removeClass('ui-icon-minus');
this.expander_icon.addClass('ui-icon-plus');
delete this.tree.expanded[this.path];
} else {
this.expander_icon.removeClass('ui-icon-plus');
this.expander_icon.addClass('ui-icon-minus');
this.tree.expanded[this.path] = this;
}
this.tree.display();
},
select_row: function() {
this.el.toggleClass('ui-state-highlight');
if (this.el.hasClass('ui-state-highlight')) {
this.tree.select_changed(this.record);
} else {
this.tree.select_changed(null);
}
}
});
Sao.View.Tree.CharColumn = Sao.class_(Object, {
init: function(model, attributes) {
this.type = 'field';
this.field = model.fields[attributes.name];
this.attributes = attributes;
},
render: function(record) {
var cell = jQuery('<span/>');
var update_text = function() {
cell.text(this.field.get_client(record));
};
record.load(this.attributes.name).done(update_text.bind(this));
return cell;
}
});
Sao.View.Tree.ButtonColumn = Sao.class_(Object, {
init: function(attributes) {
this.type = 'button';
this.attributes = attributes;
},
render: function() {
var button = jQuery('<button/>', {
'class': 'button',
'label': this.attributes.string
});
return button;
}
});
Sao.View.Form = Sao.class_(Sao.View, {});
}());

31
package.json Normal file
View file

@ -0,0 +1,31 @@
{
"name": "sao",
"title": "sao",
"description": "Tryton webclient",
"version": "0.0.1",
"homepage": "http://www.tryton.org/",
"author": {
"name": "Tryton"
},
"repository": {
"type": "hg",
"url": "http://hg.tryton.org/sandbox/sao"
},
"bugs": {
"url": "http://bugs.tryton.org/"
},
"licenses": ["GPL-3"],
"scripts": {
"test": "./node_modules/.bin/grunt"
},
"dependencies": {
"jquery": "~1.8.3",
"jquery-core": "~1.9.2"
},
"devDependencies": {
"grunt": "~0.4.0",
"grunt-contrib-jshint": "~0.1.0",
"grunt-contrib-nodeunit": "~0.1.0"
},
"keywords": ["tryton"]
}

8
tests/.jshintrc Normal file
View file

@ -0,0 +1,8 @@
{
"browser": true,
"jquery": true,
"predef": [
"QUnit",
"Sao"
]
}

File diff suppressed because it is too large Load diff