diff --git a/addons/web/static/src/js/form.js b/addons/web/static/src/js/form.js index 1ad74b90900..7057ef6c10a 100644 --- a/addons/web/static/src/js/form.js +++ b/addons/web/static/src/js/form.js @@ -289,7 +289,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }); } if (result.domain) { - // TODO: + // TODO: } this.ready = true; }, @@ -1862,7 +1862,6 @@ openerp.web.form.One2ManyDataSet = openerp.web.BufferedDataSet.extend({ }); openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({ - }); openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 19cb781e8a2..936106ac103 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -247,7 +247,7 @@ openerp.web.ViewManager = openerp.web.Widget.extend({ }, /** * Event launched when a controller has been inited. - * + * * @param {String} view_type type of view * @param {String} view the inited controller */ @@ -324,7 +324,7 @@ openerp.web.ViewManagerAction = openerp.web.ViewManager.extend({ var searchview_loaded = this.setup_search_view( searchview_id || false, search_defaults); - + // schedule auto_search if (searchview_loaded != null && this.action['auto_search']) { $.when(searchview_loaded, inital_view_loaded) @@ -668,7 +668,7 @@ openerp.web.View = openerp.web.Widget.extend({ return this.rpc('/web/action/load', { action_id: parseInt(action_data.name, 10), context: context }, handler); } else { return dataset.exec_workflow(record_id, action_data.name, handler); - } + } }, /** * Directly set a view to use instead of calling fields_view_get. This method must @@ -752,7 +752,11 @@ openerp.web.views = new openerp.web.Registry(); openerp.web.json_node_to_xml = function(node, single_quote, indent) { // For debugging purpose, this function will convert a json node back to xml // Maybe usefull for xml view editor - if (typeof(node.tag) !== 'string' || !node.children instanceof Array || !node.attrs instanceof Object) { + + if (typeof(node) === 'string') { + return node; + } + else if (typeof(node.tag) !== 'string' || !node.children instanceof Array || !node.attrs instanceof Object) { throw("Node a json node"); } indent = indent || 0; @@ -770,7 +774,7 @@ openerp.web.json_node_to_xml = function(node, single_quote, indent) { } r += ' ' + attr + '="' + vattr + '"'; } - if (node.children.length) { + if (node.children && node.children.length) { r += '>\n'; var childs = []; for (var i = 0, ii = node.children.length; i < ii; i++) { diff --git a/addons/web_kanban/__init__.py b/addons/web_kanban/__init__.py new file mode 100644 index 00000000000..355939a4922 --- /dev/null +++ b/addons/web_kanban/__init__.py @@ -0,0 +1 @@ +import controllers \ No newline at end of file diff --git a/addons/web_kanban/__openerp__.py b/addons/web_kanban/__openerp__.py new file mode 100644 index 00000000000..30a6421cca1 --- /dev/null +++ b/addons/web_kanban/__openerp__.py @@ -0,0 +1,12 @@ +{ + "name" : "Base Kanban", + "version" : "2.0", + "depends" : ["web"], + "js": [ + "static/src/js/kanban.js" + ], + "css": [ + "static/src/css/kanban.css" + ], + 'active': True +} diff --git a/addons/web_kanban/controllers/__init__.py b/addons/web_kanban/controllers/__init__.py new file mode 100644 index 00000000000..039d9715fab --- /dev/null +++ b/addons/web_kanban/controllers/__init__.py @@ -0,0 +1 @@ +import main \ No newline at end of file diff --git a/addons/web_kanban/controllers/main.py b/addons/web_kanban/controllers/main.py new file mode 100644 index 00000000000..ac1ba0f7637 --- /dev/null +++ b/addons/web_kanban/controllers/main.py @@ -0,0 +1,11 @@ +import web.common as openerpweb + +from web.controllers.main import View + +class KanbanView(View): + _cp_path = "/web_kanban/kanbanview" + + @openerpweb.jsonrequest + def load(self, req, model, view_id): + fields_view = self.fields_view_get(req, model, view_id, 'kanban') + return {'fields_view': fields_view} diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css new file mode 100644 index 00000000000..e3fac6dbafe --- /dev/null +++ b/addons/web_kanban/static/src/css/kanban.css @@ -0,0 +1,102 @@ +.openerp .oe_kanban_view .oe_column { + float: left; + width: 100%; +} + +.openerp .oe_kanban_view .ui-sortable-placeholder { + border: 1px dotted black; + visibility: visible !important; + height: 60px !important; +} + +.openerp .oe_kanban_view .oe_column_heading { + color: #000000; + font-size: 1.5em; + font-weight: bold; +} +.openerp .oe_kanban_action_button { + height: 22px; + margin: 0; +} +.openerp .oe_kanban_action_a { + text-decoration: none; +} + +.openerp .oe_kanban_box { + background: #FFF; + border: 2px solid #CCC; + border-radius: 4px; + margin-bottom: 1em; +} +.openerp .oe_kanban_box_header { + background: #EEE; + border-bottom: 1px solid #CCC; + padding: 2px; + height: 2em; + white-space: nowrap; +} +.openerp .oe_kanban_box_header h4 { + font-size: 130%; + font-weight: bold; + margin: 0; + padding: 0; +} +.openerp .oe_kanban_box_header h5 { + font-size: 120%; + font-weight: bold; + margin: 0; + padding: 2px; +} +.openerp .oe_kanban_box_header h6 { + font-size: 110%; + font-weight: normal; + margin: 0; + padding: 2px; +} +.openerp .oe_kanban_small { + font-size: 80%; +} +.openerp .oe_kanban_box_content { + padding: 4px; +} +.openerp .oe_kanban_buttons_set { + border-top: 1px dotted #CCC; +} +.openerp .oe_kanban_buttons_set .oe_kanban_left a { + border-right: 1px dotted #CCC; + padding: 2px; +} +.openerp .oe_kanban_buttons_set .oe_kanban_right a { + border-left: 1px dotted #CCC; + padding: 2px; +} +.openerp .oe_kanban_left { + float: left; +} +.openerp .oe_kanban_right { + float: right; +} +.openerp .oe_kanban_clear { + clear: both; +} +.openerp .oe_kanban_box_show_onclick { + display: none; +} +.openerp .oe_kanban_draghandle { + cursor: move; +} + +/* Custom colors */ +.openerp .oe_kanban_color_1 .oe_kanban_color_bglight, +.openerp .oe_kanban_color_1.oe_kanban_color_bglight { + background: #F5F7C4; +} +.openerp .oe_kanban_color_1 .oe_kanban_color_bgdark, +.openerp .oe_kanban_color_1.oe_kanban_color_bgdark { + background: #EEF093; +} +.openerp .oe_kanban_color_1 .oe_kanban_color_border, +.openerp .oe_kanban_color_1.oe_kanban_color_border { + border-color: #DFE32D; +} + diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js new file mode 100644 index 00000000000..66c5a5e2d1c --- /dev/null +++ b/addons/web_kanban/static/src/js/kanban.js @@ -0,0 +1,398 @@ +openerp.web_kanban = function (openerp) { + +QWeb.add_template('/web_kanban/static/src/xml/web_kanban.xml'); +openerp.web.views.add('kanban', 'openerp.web_kanban.KanbanView'); +openerp.web_kanban.KanbanView = openerp.web.View.extend({ + init: function (parent, element_id, dataset, view_id, options) { + this._super(parent, element_id); + this.set_default_options(options); + this.dataset = dataset; + this.model = dataset.model; + this.domain = dataset.domain; + this.context = dataset.context; + this.view_id = view_id; + this.fields_view = {}; + this.group_by = []; + this.source_index = {}; + this.all_display_data = false; + this.groups = []; + this.qweb = new QWeb2.Engine(); + }, + start: function() { + return this.rpc("/web_kanban/kanbanview/load", + {"model": this.model, "view_id": this.view_id}, this.on_loaded); + }, + on_loaded: function(data) { + var self = this; + this.fields_view = data.fields_view; + this.add_qweb_template(); + if (this.qweb.has_template('kanban-box')) { + self.dataset.read_slice(_.keys(self.fields_view.fields), { + context: self.dataset.get_context(), + domain: self.dataset.get_domain() + }, function (records) { + self.all_display_data = [{'records': records, 'value': false, 'header': false, 'ids': self.dataset.ids}]; + self.on_show_data(self.all_display_data); + } + ); + } + }, + add_qweb_template: function() { + for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) { + var child = this.fields_view.arch.children[i]; + if (child.tag === "templates") { + this.transform_qweb_template(child); + this.qweb.add_template(openerp.web.json_node_to_xml(child, true)); + break; + } + } + }, + do_get_kanban_color: function(variable) { + return 'oe_kanban_color_1'; + }, + transform_qweb_template: function(node) { + switch (node.tag) { + case 'field': + node.tag = 't'; + node.attrs['t-esc'] = node.attrs['name'] + '.value'; + break + case 'button': + case 'a': + var type = node.attrs.type || ''; + if (_.indexOf('action,object,edit,delete,'.split(','), type) !== -1) { + _.each(node.attrs, function(v, k) { + node.attrs['data-' + k] = v; + delete(node.attrs[k]); + }); + if (node.attrs['data-states']) { + var states = _.map(node.attrs['data-states'].split(','), function(state) { + return "state.value == '" + _.trim(state) + "'"; + }); + node.attrs['t-if'] = states.join(' or '); + } + if (node.attrs['data-string']) { + node.attrs.title = node.attrs['data-string']; + } + if (node.attrs['data-icon']) { + node.children = [{ + tag: 'img', + attrs: { + src: '/web/static/src/img/icons/' + node.attrs['data-icon'] + '.png', + width: '16', + height: '16' + } + }]; + } + if (node.tag == 'a') { + node.attrs.href = '#'; + } else { + node.attrs.type = 'button'; + } + node.attrs['class'] = (node.attrs['class'] || '') + ' oe_kanban_action oe_kanban_action_' + node.tag; + } + break; + } + if (node.children) { + for (var i = 0, ii = node.children.length; i < ii; i++) { + this.transform_qweb_template(node.children[i]); + } + } + }, + on_show_data: function(data) { + var self = this; + this.$element.html(QWeb.render("KanbanView", {"data": data})); + this.on_reload_kanban(); + var drag_handel = false; + if (this.$element.find(".oe_kanban_draghandle").length > 0) { + drag_handel = ".oe_kanban_draghandle"; + } + this.$element.find(".oe_column").sortable({ + connectWith: ".oe_column", + handle : drag_handel, + start: function(event, ui) { + self.source_index['index'] = ui.item.index(); + self.source_index['column'] = ui.item.parent().attr('id'); + }, + stop: self.on_receive_record, + }); + this.$element.find(".oe_column").disableSelection() + this.$element.find('button.oe_kanban_button_new').click(this.do_add_record); + }, + on_button_click: function (button_attrs, record_id) { + var self = this; + if (this.groups.length) { + _.each(this.groups, function (group) { + group.list([], + function (groups) {}, + function (dataset) { + dataset.read_slice([], {}, function(records) { + var index = parseInt(_.indexOf(dataset.ids, record_id)); + if(index >= 0) { + self.on_confirm_click(dataset, button_attrs, index, record_id); + } + }); + } + ); + }); + } else { + var index = parseInt(_.indexOf(self.dataset.ids, record_id)); + if (index >= 0) { + _.extend(self.dataset, {domain: self.domain, context: self.context}); + self.on_confirm_click(self.dataset, button_attrs, index, record_id); + } + } + }, + on_confirm_click: function (dataset, button_attrs, index, record_id) { + if (button_attrs.type == 'edit') { + this.do_edit_record(dataset, index); + } else { + this.on_execute_button_click(dataset, button_attrs, record_id); + } + }, + do_add_record: function () { + this.do_edit_record(this.dataset, null); + }, + do_edit_record: function (dataset, index) { + var self = this; + _.extend(this.dataset, { + domain: dataset.domain, + context: dataset.get_context() + }).read_slice([], {}, function () { + self.dataset.index = index; + self.do_switch_view('form'); + }); + }, + do_delete: function (id) { + var self = this; + return $.when(this.dataset.unlink([id])).then(function () { + self.drop_records(id); + }); + }, + drop_records: function (id) { + var self = this; + _.each(self.all_display_data, function(data, index) { + _.each(data.records, function(record, index_row) { + if (parseInt(record.id) == id) { + self.all_display_data[index]['records'].splice(index_row, 1); + self.all_display_data[index]['ids'].splice(index_row, 1); + return false; + } + }); + }); + self.$element.find("#main_" + id).remove(); + }, + on_execute_button_click: function (dataset, button_attrs, record_id) { + var self = this; + this.execute_action( + button_attrs, dataset, + record_id, function () { + var count = 1; + _.each(self.all_display_data, function(data, index) { + self.dataset.read_ids( data.ids, [], function(records){ + self.all_display_data[index].records = records; + if(self.all_display_data.length == count) { + self.do_actual_search(); + } + count++; + }); + }); + } + ); + }, + on_receive_record: function (event, ui) { + var self = this; + var from = ui.item.index(); + var search_action = false; + var to = ui.item.prev().index() || 0; + if (!ui.item.attr("id")) { + return false; + } + // TODO fme: check what was this sequence + if (self.fields_view.fields.sequence && (self.source_index.index >= 0 && self.source_index.index != from) || + (self.source_index.column && self.source_index.column != ui.item.parent().attr('id'))) { + var child_record = ui.item.parent().children(); + var data, sequence = 1, index = to; + child_record.splice(0, to); + var flag = false; + if (to >= 0 && child_record) { + var record_id = parseInt($(child_record).attr("id").split("_")[1]); + if (record_id) { + _.each(self.all_display_data, function(data, index) { + _.each(data.records, function(record, index_row) { + if(record_id == record.id && record.sequence) { + sequence = record.sequence; + flag = true; + return false; + } + }); + if(flag) {return false;} + }); + } + } + _.each(child_record, function (child) { + var child_id = parseInt($(child).attr("id").split("_")[1]); + if (child_id) { + flag = false; + _.each(self.all_display_data, function(data, index) { + _.each(data.records, function(record, index_row) { + if(parseInt(record.id) == child_id) { + self.all_display_data[index]['records'][index_row]['sequence'] = sequence; + flag = true; + return false; + } + }); + if (flag) {return false;} + }); + self.dataset.write(child_id, {sequence: sequence}); + sequence++; + search_action = true; + } + }); + } + if (self.group_by.length > 0 && self.source_index.column && self.source_index.column != ui.item.parent().attr('id')) { + var value = ui.item.closest("td").attr("id"); + if (value) { + var data_val = {}; + var wirte_id = parseInt(ui.item.attr("id").split("_")[1]); + value = value.split("_")[1]; + if (value == 'false') { + value = false; + } + var update_record = false; + _.each(self.all_display_data, function(data, index) { + _.each(data.records, function(record, index_row) { + if(parseInt(record.id) == wirte_id) { + self.all_display_data[index]['records'][index_row][self.group_by[0]] = value; + update_record = self.all_display_data[index]['records'].splice(index_row,1) + return false; + } + }); + if (update_record) {return false;} + }); + _.each(self.all_display_data, function(data, index) { + if (data.value == value || (data.value == 'false' && value == false)) { + self.all_display_data[index]['records'].push(update_record[0]); + } + }); + data_val[self.group_by[0]] = value; + self.dataset.write(wirte_id, data_val); + search_action = true; + } + } + if (search_action) { + self.on_reload_kanban(); + } + this.source_index = {}; + }, + on_reload_kanban: function (){ + var self = this; + _.each(self.all_display_data, function(data, index) { + if (data.records.length > 0){ + _.each(data.records, function(record) { + self.$element.find("#main_" + record.id).children().remove(); + self.$element.find("#main_" + record.id).append(self.qweb.render('kanban-box', self.do_transform_record(record))); + }); + } else { + self.$element.find("#column_" + data.value).remove(); + self.all_display_data.splice(index, 1); + } + }); + this.$element.find('.oe_kanban_action').click(function() { + var record_id = $(this).closest(".oe_kanban_record").attr("id"); + if (record_id) { + record_id = parseInt(record_id.split("_")[1]) + if (record_id) { + if ($(this).data("type") == "delete") { + self.do_delete(record_id); + } else { + var button_attrs = $(this).data() + self.on_button_click(button_attrs, record_id); + } + } + } + }); + this.$element.find('.oe_kanban_record').click(function() { + $(this).find('.oe_kanban_box_show_onclick').removeClass('oe_kanban_box_show_onclick'); + }); + }, + do_transform_record: function(record) { + var self = this, + new_record = {}; + _.each(record, function(value, name) { + var r = _.clone(self.fields_view.fields[name]); + r.raw_value = value; + r.value = openerp.web.format_value(value, r); + new_record[name] = r; + }); + new_record.__kanban_color = this.do_get_kanban_color; + return new_record; + }, + do_search: function (domains, contexts, group_by) { + var self = this; + this.rpc('/web/session/eval_domain_and_context', { + domains: domains, + contexts: contexts, + group_by_seq: group_by + }, function (results) { + self.domain = results.domain; + self.context = results.context; + self.group_by = results.group_by; + self.do_actual_search(); + }); + }, + do_actual_search : function () { + var self = this; + self.datagroup = new openerp.web.DataGroup(self, self.model, self.domain, self.context, self.group_by || []); + self.dataset.context = self.context; + self.dataset.domain = self.domain; + self.datagroup.list([], + function (groups) { + self.groups = groups; + self.do_render_group(groups); + }, + function (dataset) { + self.domain = dataset.domain; + self.context = dataset.context; + self.groups = []; + self.dataset.read_slice([], {}, function(records) { + self.all_display_data = [{'records': records, 'value':false, 'header' : false, 'ids': self.dataset.ids}]; + self.$element.find(".oe_kanban_view").remove(); + self.on_show_data(self.all_display_data); + }); + } + ); + }, + do_render_group : function (datagroups) { + this.all_display_data = []; + var self = this; + _.each(datagroups, function (group) { + self.dataset.context = group.context; + self.dataset.domain = group.domain; + var group_name = group.value; + var group_value = group.value; + if (!group.value) { + group_name = "Undefined"; + group_value = 'false'; + } else if (group.value instanceof Array) { + group_name = group.value[1]; + group_value = group.value[0]; + } + self.dataset.read_slice([], {}, function(records) { + self.all_display_data.push({"value" : group_value, "records": records, 'header':group_name, 'ids': self.dataset.ids}); + if (datagroups.length == self.all_display_data.length) { + self.$element.find(".oe_kanban_view").remove(); + self.on_show_data(self.all_display_data); + } + }); + }); + }, + do_show: function () { + this.$element.show(); + }, + do_hide: function () { + this.$element.hide(); + } +}); +}; + +// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web_kanban/static/src/xml/web_kanban.xml b/addons/web_kanban/static/src/xml/web_kanban.xml new file mode 100644 index 00000000000..564761c955a --- /dev/null +++ b/addons/web_kanban/static/src/xml/web_kanban.xml @@ -0,0 +1,25 @@ +