From dcb9634cea8539e71c745096b420bc8b6ea25db3 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Wed, 30 Mar 2011 16:00:48 +0200 Subject: [PATCH] renames part4 bzr revid: al@openerp.com-20110330140048-qm5m28ns44hkqzvn --- addons/base/static/src/js/chrome.js | 2 +- addons/base/static/src/js/data.js | 277 +++++++ addons/base/static/src/js/form.js | 539 +++++++++++++ addons/base/static/src/js/list.js | 83 ++ addons/base/static/src/js/search.js | 1122 +-------------------------- addons/base/static/src/js/tests.js | 351 +++++++++ addons/base/static/src/js/views.js | 257 ++++++ 7 files changed, 1509 insertions(+), 1122 deletions(-) create mode 100644 addons/base/static/src/js/data.js create mode 100644 addons/base/static/src/js/form.js create mode 100644 addons/base/static/src/js/list.js create mode 100644 addons/base/static/src/js/tests.js create mode 100644 addons/base/static/src/js/views.js diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index 1cf78618817..6782de9ed56 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -2,7 +2,7 @@ * OpenERP base library *---------------------------------------------------------*/ -openerp.base$chrome = function(openerp) { +openerp.addons.base.chrome = function(openerp) { openerp.base.callback = function(obj, method) { // openerp.base.callback( obj, methods, [arg1, arg2, ... ] ) diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js new file mode 100644 index 00000000000..d25bead7b7a --- /dev/null +++ b/addons/base/static/src/js/data.js @@ -0,0 +1,277 @@ + +openerp.addons.base.data = function(openerp) { +/** + * Management interface between views and the collection of selected OpenERP + * records (represents the view's state?) + */ +openerp.base.DataGroup = openerp.base.Controller.extend({ + init: function(session) { + this._super(session, null); + }, +}); + +/** + * Management interface between views and the collection of selected OpenERP + * records (represents the view's state?) + */ +openerp.base.DataSet = openerp.base.Controller.extend({ + init: function(session, model) { + this._super(session); + this.model = model; + + this._fields = null; + + this._ids = []; + this._active_ids = null; + this._active_id_index = 0; + + this._sort = []; + this._domain = []; + this._context = {}; + }, + start: function() { + // TODO: fields_view_get fields selection? + this.rpc("/base/dataset/fields", {"model":this.model}, this.on_fields); + }, + on_fields: function(result) { + this._fields = result._fields; + this.on_ready(); + }, + + /** + * Fetch all the records selected by this DataSet, based on its domain + * and context. + * + * Fires the on_ids event. + * + * @param {Number} [offset=0] The index from which selected records should be returned + * @param {Number} [limit=null] The maximum number of records to return + * @returns itself + */ + // ADD CALLBACK + fetch: function (offset, limit) { + offset = offset || 0; + limit = limit || null; + this.rpc('/base/dataset/find', { + model: this.model, + fields: this._fields, + domain: this._domain, + context: this._context, + sort: this._sort, + offset: offset, + limit: limit + }, _.bind(function (records) { + var data_records = _.map( + records, function (record) { + return new openerp.base.DataRecord( + this.session, this.model, + this._fields, record); + }, this); + + this.on_fetch(data_records, { + offset: offset, + limit: limit, + domain: this._domain, + context: this._context, + sort: this._sort + }); + }, this)); + return this; + }, + + + /** REMOVE + * @event + * + * Fires after the DataSet fetched the records matching its internal ids selection + * + * @param {Array} records An array of the DataRecord fetched + * @param event The on_fetch event object + * @param {Number} event.offset the offset with which the original DataSet#fetch call was performed + * @param {Number} event.limit the limit set on the original DataSet#fetch call + * @param {Array} event.domain the domain set on the DataSet before DataSet#fetch was called + * @param {Object} event.context the context set on the DataSet before DataSet#fetch was called + * @param {Array} event.sort the sorting criteria used to get the ids + */ + on_fetch: function (records, event) { }, + + /** + * Fetch all the currently active records for this DataSet (records selected via DataSet#select) + * + * @returns itself + */ + // ADD fields and callback + active_ids: function () { + this.rpc('/base/dataset/get', { + ids: this.get_active_ids(), + model: this.model + }, _.bind(function (records) { + this.on_active_ids(_.map( + records, function (record) { + return new openerp.base.DataRecord( + this.session, this.model, + this._fields, record); + }, this)); + }, this)); + return this; + }, + + + /** REMIVE + * @event + * + * Fires after the DataSet fetched the records matching its internal active ids selection + * + * @param {Array} records An array of the DataRecord fetched + */ + on_active_ids: function (records) { }, + + /** + * Fetches the current active record for this DataSet + * + * @returns itself + */ + // ADD fields and callback + active_id: function () { + this.rpc('/base/dataset/get', { + ids: [this.get_active_id()], + model: this.model + }, _.bind(function (records) { + var record = records[0]; + this.on_active_id( + record && new openerp.base.DataRecord( + this.session, this.model, + this._fields, record)); + }, this)); + return this; + }, + + + /** REMOVE + * Fires after the DataSet fetched the record matching the current active record + * + * @param record the record matching the provided id, or null if there is no record for this id + */ + on_active_id: function (record) { + + }, + + /** + * Configures the DataSet + * + * @param options DataSet options + * @param {Array} options.domain the domain to assign to this DataSet for filtering + * @param {Object} options.context the context this DataSet should use during its calls + * @param {Array} options.sort the sorting criteria for this DataSet + * @returns itself + */ + set: function (options) { + if (options.domain) { + this._domain = _.clone(options.domain); + } + if (options.context) { + this._context = _.clone(options.context); + } + if (options.sort) { + this._sort = _.clone(options.sort); + } + return this; + }, + + /** + * Activates the previous id in the active sequence. If there is no previous id, wraps around to the last one + * @returns itself + */ + prev: function () { + this._active_id_index -= 1; + if (this._active_id_index < 0) { + this._active_id_index = this._active_ids.length - 1; + } + return this; + }, + /** + * Activates the next id in the active sequence. If there is no next id, wraps around to the first one + * @returns itself + */ + next: function () { + this._active_id_index += 1; + if (this._active_id_index >= this._active_ids.length) { + this._active_id_index = 0; + } + return this; + }, + + /** + * Sets active_ids by value: + * + * * Activates all ids part of the current selection + * * Sets active_id to be the first id of the selection + * + * @param {Array} ids the list of ids to activate + * @returns itself + */ + select: function (ids) { + this._active_ids = ids; + this._active_id_index = 0; + return this; + }, + /** + * Fetches the ids of the currently selected records, if any. + */ + get_active_ids: function () { + return this._active_ids; + }, + /** + * Sets the current active_id by value + * + * If there are no active_ids selected, selects the provided id as the sole active_id + * + * If there are ids selected and the provided id is not in them, raise an error + * + * @param {Object} id the id to activate + * @returns itself + */ + activate: function (id) { + if(!this._active_ids) { + this._active_ids = [id]; + this._active_id_index = 0; + } else { + var index = _.indexOf(this._active_ids, id); + if (index == -1) { + throw new Error( + "Could not find id " + id + + " in array [" + this._active_ids.join(', ') + "]"); + } + this._active_id_index = index; + } + return this; + }, + /** + * Fetches the id of the current active record, if any. + * + * @returns record? record id or null + */ + get_active_id: function () { + if (!this._active_ids) { + return null; + } + return this._active_ids[this._active_id_index]; + } +}); + +openerp.base.DataRecord = openerp.base.Controller.extend({ + init: function(session, model, fields, values) { + this._super(session, null); + this.model = model; + this.id = values.id || null; + this.fields = fields; + this.values = values; + }, + on_change: function() { + }, + on_reload: function() { + } +}); +}; + +// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js new file mode 100644 index 00000000000..7e4ff3dde02 --- /dev/null +++ b/addons/base/static/src/js/form.js @@ -0,0 +1,539 @@ + +openerp.addons.base.form = function (openerp) { + +openerp.base.FormView = openerp.base.Controller.extend({ + init: function(session, element_id, dataset, view_id) { + this._super(session, element_id); + this.dataset = dataset; + this.model = dataset.model; + this.view_id = view_id; + this.fields_views = {}; + this.widgets = {}; + this.widgets_counter = 0; + this.fields = {}; + this.datarecord = {}; + this.ready = false; + }, + start: function() { + //this.log('Starting FormView '+this.model+this.view_id) + this.rpc("/base/formview/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.log(this.fields_view); + + var frame = new openerp.base.WidgetFrame(this, this.fields_view.arch); + + this.$element.html(QWeb.render("FormView", { "frame": frame, "view": this })); + _.each(this.widgets, function(w) { + w.start(); + }); + this.$element.find('button.form_save').click(this.do_save); + +// this.dataset.on_active_id.add(this.on_record_loaded); +// this.dataset.active_id(fields of the form, this.on_record_loaded); + }, + on_next: function() { +// this.dataset.next(); +// this.dataset.active_id(fields of the form, this.on_record_loaded); + }, + on_prev: function() { + +// this.dataset.prev(); +// this.dataset.active_id(fields of the form, this.on_record_loaded); + }, + on_record_loaded: function(record) { + this.datarecord = record; + for (var f in this.fields) { + this.fields[f].set_value(this.datarecord.values[f]); + } + this.on_form_changed(); + this.ready = true; + }, + on_form_changed: function(widget) { + for (var w in this.widgets) { + w = this.widgets[w]; + w.process_attrs(); + w.update_dom(); + } + if (widget) { + // TODO: check if and trigger + // if (onchange for this field) { + // this.ready = false; + // rpc - process.callback ( this.ready = true; ) + // } + } + }, + do_save: function() { + if (!this.ready) { + return false; + } + var invalid = false; + var values = {}; + for (var f in this.fields) { + f = this.fields[f]; + if (f.invalid) { + invalid = true; + } else { + values[f.name] = f.value; + } + } + if (invalid) { + this.on_invalid(); + } else { + console.log("Save form", values); + // TODO: save values via datarecord + // rpc - save.callbacl on_saved + } + }, + on_invalid: function() { + }, + on_saved: function() { + // Check response for exceptions, display error + } +}); + +openerp.base.Widget = openerp.base.Controller.extend({ + // TODO Change this to init: function(view, node) { and use view.session and a new element_id for the super + // it means that widgets are special controllers + init: function(view, node) { + this.view = view; + this.node = node; + this.attrs = eval('(' + (this.node.attrs.attrs || '{}') + ')'); + this.type = this.type || node.tag; + this.element_name = this.element_name || this.type; + this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_"); + + this._super(this.view.session, this.element_id); + + this.view.widgets[this.element_id] = this; + this.children = node.children; + this.colspan = parseInt(node.attrs.colspan || 1); + this.template = "Widget"; + + this.string = this.string || node.attrs.string; + this.help = this.help || node.attrs.help; + this.invisible = (node.attrs.invisible == '1'); + }, + start: function() { + this.$element = $('#' + this.element_id); + }, + process_attrs: function() { + for (var a in this.attrs) { + this[a] = this.eval_attrs(this.attrs[a]); + } + }, + eval_attrs: function(expr) { + var stack = []; + for (var i = 0; i < expr.length; i++) { + var ex = expr[i]; + if (ex.length == 1) { + stack.push(ex[0]); + continue; + } + + var field = this.view.fields[ex[0]].value; + var op = ex[1]; + var val = ex[2]; + + switch (op.toLowerCase()) { + case '=': + case '==': + stack.push(field == val); + break; + case '!=': + case '<>': + stack.push(field != val); + break; + case '<': + stack.push(field < val); + break; + case '>': + stack.push(field > val); + break; + case '<=': + stack.push(field <= val); + break; + case '>=': + stack.push(field >= val); + break; + case 'in': + stack.push(_.indexOf(val, field) > -1); + break; + case 'not in': + stack.push(_.indexOf(val, field) == -1); + break; + default: + this.log("Unsupported operator in attrs :", op); + } + } + + for (var j = stack.length-1; j >- 1; j--) { + switch (stack[j]) { + case '|': + var result = stack[j + 1] || stack[j + 2]; + stack.splice(j, 3, result); + break; + case '&': + var result = stack[j + 1] && stack[j + 2]; + stack.splice(j, 3, result); + break; + } + } + return _.indexOf(stack, false) == -1; + }, + update_dom: function() { + this.$element.toggle(!this.invisible); + }, + render: function() { + var template = this.template; + return QWeb.render(template, { "widget": this }); + } +}); + +openerp.base.WidgetFrame = openerp.base.Widget.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "WidgetFrame"; + this.columns = node.attrs.col || 4; + this.x = 0; + this.y = 0; + this.table = []; + this.add_row(); + for (var i = 0; i < node.children.length; i++) { + var n = node.children[i]; + if (n.tag == "newline") { + this.add_row(); + } else { + this.handle_node(n); + } + } + this.set_row_cells_with(this.table[this.table.length - 1]); + }, + add_row: function(){ + if (this.table.length) { + this.set_row_cells_with(this.table[this.table.length - 1]); + } + var row = []; + this.table.push(row); + this.x = 0; + this.y += 1; + return row; + }, + set_row_cells_with: function(row) { + for (var i = 0; i < row.length; i++) { + var w = row[i]; + if (w.is_field_label) { + w.width = "1%"; + if (row[i + 1]) { + row[i + 1].width = Math.round((100 / this.columns) * (w.colspan + 1) - 1) + '%'; + } + } else if (w.width === undefined) { + w.width = Math.round((100 / this.columns) * w.colspan) + '%'; + } + } + }, + handle_node: function(n) { + var type = this.view.fields_view.fields[n.attrs.name] || {}; + var widget_type = n.attrs.widget || type.type || n.tag; + if (openerp.base.widgets[widget_type]) { + var widget = new openerp.base.widgets[widget_type](this.view, n); + if (n.tag == 'field' && n.attrs.nolabel != '1') { + var label = new openerp.base.widgets['label'](this.view, n); + label["for"] = widget; + this.add_widget(label); + } + this.add_widget(widget); + } else { + this.log("Unhandled widget type : " + widget_type, n); + } + }, + add_widget: function(w) { + if (!w.invisible) { + var current_row = this.table[this.table.length - 1]; + if (current_row.length && (this.x + w.colspan) > this.columns) { + current_row = this.add_row(); + } + current_row.push(w); + this.x += w.colspan; + } + return w; + } +}); + +openerp.base.WidgetNotebook = openerp.base.Widget.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "WidgetNotebook"; + this.pages = []; + for (var i = 0; i < node.children.length; i++) { + var n = node.children[i]; + if (n.tag == "page") { + var page = new openerp.base.WidgetFrame(this.view, n); + this.pages.push(page); + } + } + }, + start: function() { + this._super.apply(this, arguments); + this.$element.tabs(); + } +}); + +openerp.base.WidgetSeparator = openerp.base.Widget.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "WidgetSeparator"; + } +}); + +openerp.base.WidgetButton = openerp.base.Widget.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "WidgetButton"; + } +}); + +openerp.base.WidgetLabel = openerp.base.Widget.extend({ + init: function(view, node) { + this.is_field_label = true; + this.element_name = 'label_' + node.attrs.name; + + this._super(view, node); + + this.template = "WidgetLabel"; + this.colspan = 1; + }, + render: function () { + if (this['for'] && this.type !== 'label') { + return QWeb.render(this.template, {widget: this['for']}); + } + // Actual label widgets should not have a false and have type label + return QWeb.render(this.template, {widget: this}); + } +}); + +openerp.base.Field = openerp.base.Widget.extend({ + init: function(view, node) { + this.name = node.attrs.name; + this.value = undefined; + view.fields[this.name] = this; + this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type; + this.element_name = "field_" + this.name + "_" + this.type; + + this._super(view, node); + + if (node.attrs.nolabel != '1' && this.colspan > 1) { + this.colspan--; + } + // this.datarecord = this.view.datarecord ?? + this.field = view.fields_view.fields[node.attrs.name]; + this.string = node.attrs.string || this.field.string; + this.help = node.attrs.help || this.field.help; + this.nolabel = (node.attrs.nolabel == '1'); + this.readonly = (node.attrs.readonly == '1'); + this.required = (node.attrs.required == '1'); + this.invalid = false; + }, + set_value: function(value) { + this.value = value; + }, + get_value: function(value) { + return value; + }, + update_dom: function() { + this._super.apply(this, arguments); + this.$element.toggleClass('disabled', this.readonly); + this.$element.toggleClass('required', this.required); + }, + on_ui_change: function() { + this.view.on_form_changed(this); + } +}); + +openerp.base.FieldChar = openerp.base.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldChar"; + }, + start: function() { + this._super.apply(this, arguments); + this.$element.find('input').change(this.on_ui_change); + }, + set_value: function(value) { + this._super.apply(this, arguments); + if (value != null && value !== false) { + this.$element.find('input').val(value); + } + }, + get_value: function() { + }, + update_dom: function() { + this._super.apply(this, arguments); + this.$element.find('input').attr({ + 'disabled' : this.readonly, + 'required' : this.required + }); + }, + on_ui_change: function() { + this.value = this.$element.find('input').val(); + this.invalid = this.required && this.value == ""; + this._super.apply(this, arguments); + } +}); + +openerp.base.FieldEmail = openerp.base.FieldChar.extend({ +}); + +openerp.base.FieldUrl = openerp.base.FieldChar.extend({ +}); + +openerp.base.FieldFloat = openerp.base.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldChar"; + }, + start: function() { + this._super.apply(this, arguments); + this.$element.find('input').change(this.on_ui_change); + }, + set_value: function(value) { + this._super.apply(this, arguments); + if (value != null && value !== false) { + this.$element.find('input').val(value.toFixed(2)); + } + }, + get_value: function() { + }, + update_dom: function() { + this._super.apply(this, arguments); + this.$element.find('input').attr({ + 'disabled' : this.readonly, + 'required' : this.required + }); + }, + on_ui_change: function() { + this.value = this.$element.find('input').val(); + this.invalid = this.required && this.value == ""; + this._super.apply(this, arguments); + } +}); + +openerp.base.FieldText = openerp.base.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldText"; + }, + set_value: function(value) { + this._super.apply(this, arguments); + if (value != null && value !== false) { + this.$element.find('textarea').val(value); + } + }, + get_value: function() { + return this.$element.find('textarea').val(); + } +}); + +openerp.base.FieldBoolean = openerp.base.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldBoolean"; + }, + set_value: function(value) { + this._super.apply(this, arguments); + this.$element.find('input')[0].checked = value; + }, + get_value: function() { + this.$element.find('input')[0].checked; + } +}); + +openerp.base.FieldDate = openerp.base.FieldChar.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldDate"; + } +}); + +openerp.base.FieldDatetime = openerp.base.FieldChar.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldDatetime"; + } +}); + +openerp.base.FieldTextXml = openerp.base.Field.extend({ +// to replace view editor +}); + +openerp.base.FieldSelection = openerp.base.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldSelection"; + }, + set_value: function(value) { + this._super.apply(this, arguments); + if (value != null && value !== false) { + this.$element.find('select').val(value); + } + }, + get_value: function() { + return this.$element.find('select').val(); + } +}); + +openerp.base.FieldMany2One = openerp.base.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldMany2One"; + } +}); + +openerp.base.FieldOne2Many = openerp.base.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldOne2Many"; + } +}); + +openerp.base.FieldMany2Many = openerp.base.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldMany2Many"; + } +}); + +openerp.base.FieldReference = openerp.base.Field.extend({ + init: function(view, node) { + this._super(view, node); + this.template = "FieldReference"; + } +}); + +openerp.base.widgets = { + 'group' : openerp.base.WidgetFrame, + 'notebook' : openerp.base.WidgetNotebook, + 'separator' : openerp.base.WidgetSeparator, + 'label' : openerp.base.WidgetLabel, + 'char' : openerp.base.FieldChar, + 'email' : openerp.base.FieldEmail, + 'url' : openerp.base.FieldUrl, + 'text' : openerp.base.FieldText, + 'date' : openerp.base.FieldDate, + 'datetime' : openerp.base.FieldDatetime, + 'selection' : openerp.base.FieldSelection, + 'many2one' : openerp.base.FieldMany2One, + 'many2many' : openerp.base.FieldMany2Many, + 'one2many' : openerp.base.FieldOne2Many, + 'one2many_list' : openerp.base.FieldOne2Many, + 'reference' : openerp.base.FieldReference, + 'boolean' : openerp.base.FieldBoolean, + 'float' : openerp.base.FieldFloat, + 'button' : openerp.base.WidgetButton +}; + +}; + +// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js new file mode 100644 index 00000000000..79e9d171550 --- /dev/null +++ b/addons/base/static/src/js/list.js @@ -0,0 +1,83 @@ + +openerp.addons.base.form = function (openerp) { + +openerp.base.ListView = openerp.base.Controller.extend({ + init: function(session, element_id, dataset, view_id) { + this._super(session, element_id); + this.dataset = dataset; + this.model = dataset.model; + this.view_id = view_id; + this.name = ""; + + this.cols = []; + + this.$table = null; + this.colnames = []; + this.colmodel = []; + + this.event_loading = false; // TODO in the future prevent abusive click by masking + }, + start: function() { + //this.log('Starting ListView '+this.model+this.view_id) + this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); + }, + on_loaded: function(data) { + this.fields_view = data.fields_view; + //this.log(this.fields_view); + this.name = "" + this.fields_view.arch.attrs.string; + this.$element.html(QWeb.render("ListView", {"fields_view": this.fields_view})); + this.$table = this.$element.find("table"); + this.cols = []; + this.colnames = []; + this.colmodel = []; + // TODO uss a object for each col, fill it with view and fallback to dataset.model_field + var tree = this.fields_view.arch.children; + for(var i = 0; i < tree.length; i++) { + var col = tree[i]; + if(col.tag == "field") { + this.cols.push(col.attrs.name); + this.colnames.push(col.attrs.name); + this.colmodel.push({ name: col.attrs.name, index: col.attrs.name }); + } + } + this.dataset.fields = this.cols; + this.dataset.on_fetch.add(this.do_fill_table); + + var width = this.$element.width(); + this.$table.jqGrid({ + datatype: "local", + height: "100%", + rowNum: 100, + //rowList: [10,20,30], + colNames: this.colnames, + colModel: this.colmodel, + //pager: "#plist47", + viewrecords: true, + caption: this.name + }).setGridWidth(width); + + var self = this; + $(window).bind('resize', function() { + self.$element.children().hide(); + self.$table.setGridWidth(self.$element.width()); + self.$element.children().show(); + }).trigger('resize'); + }, + do_fill_table: function(records) { + this.log("do_fill_table"); + + this.$table + .clearGridData() + .addRowData('id', _.map(records, function (record) { + return record.values; + })); + + } +}); + +openerp.base.TreeView = openerp.base.Controller.extend({ +}); + +}; + +// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js index 4b5f69a967a..238df7cb9ea 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -1,500 +1,4 @@ -/*--------------------------------------------------------- - * OpenERP base library - *---------------------------------------------------------*/ - -openerp.base$views = function(openerp) { - -// process all kind of actions -openerp.base.ActionManager = openerp.base.Controller.extend({ - init: function(session, element_id) { - this._super(session, element_id); - this.action = null; - this.viewmanager = null; - }, - /** - * Process an action - * Supported actions: act_window - */ - do_action: function(action) { - // instantiate the right controllers by understanding the action - this.action = action; - if(action.type == "ir.actions.act_window") { - this.viewmanager = new openerp.base.ViewManager(this.session,this.element_id); - this.viewmanager.do_action_window(action); - this.viewmanager.start(); - } - } -}); - -// This will be ViewManager Abstract/Common -openerp.base.ViewManager = openerp.base.Controller.extend({ - init: function(session, element_id) { - this._super(session, element_id); - this.action = null; - this.dataset = null; - this.searchview_id = false; - this.searchview = null; - this.search_visible = true; - // this.views = { "list": { "view_id":1234, "controller": instance} } - this.views = {}; - }, - start: function() { - }, - on_mode_switch: function(view_type) { - for (var i in this.views) { - this.views[i].controller.$element.toggle(i === view_type); - } - }, - /** - * Extract search view defaults from the current action's context. - * - * These defaults are of the form {search_default_*: value} - * - * @returns {Object} a clean defaults mapping of {field_name: value} - */ - search_defaults: function () { - var defaults = {}; - _.each(this.action.context, function (value, key) { - var match = /^search_default_(.*)$/.exec(key); - if (match) { - defaults[match[1]] = value; - } - }); - return defaults; - }, - do_action_window: function(action) { - var self = this; - var prefix_id = "#" + this.element_id; - this.action = action; - this.dataset = new openerp.base.DataSet(this.session, action.res_model); - this.dataset.start(); - - this.$element.html(QWeb.render("ViewManager", {"prefix": this.element_id, views: action.views})); - - this.searchview_id = false; - if(this.search_visible && action.search_view_id) { - this.searchview_id = action.search_view_id[0]; - var searchview = this.searchview = new openerp.base.SearchView( - this.session, this.element_id + "_search", - this.dataset, this.searchview_id, - this.search_defaults()); - searchview.on_search.add(this.do_search); - searchview.start(); - - if (action['auto_search']) { - searchview.on_loaded.add_last( - searchview.do_search); - } - } - for(var i = 0; i < action.views.length; i++) { - var view_id, controller; - view_id = action.views[i][0]; - if(action.views[i][1] == "tree") { - controller = new openerp.base.ListView(this.session, this.element_id + "_view_tree", this.dataset, view_id); - controller.start(); - this.views.tree = { view_id: view_id, controller: controller }; - this.$element.find(prefix_id + "_button_tree").bind('click',function(){ - self.on_mode_switch("tree"); - }); - } else if(action.views[i][1] == "form") { - controller = new openerp.base.FormView(this.session, this.element_id + "_view_form", this.dataset, view_id); - controller.start(); - this.views.form = { view_id: view_id, controller: controller }; - this.$element.find(prefix_id + "_button_form").bind('click',function(){ - self.on_mode_switch("form"); - }); - } - } - // switch to the first one in sequence - this.on_mode_switch("tree"); - }, - // create when root, also add to parent when o2m - on_create: function() { - }, - on_remove: function() { - }, - on_edit: function() { - }, - do_search: function (domains, contexts, groupbys) { - var self = this; - this.rpc('/base/session/eval_domain_and_context', { - domains: domains, - contexts: contexts, - group_by_seq: groupbys - }, function (results) { - // TODO: handle non-empty results.group_by with read_group - self.dataset.set({ - context: results.context, - domain: results.domain - }).fetch(0, self.action.limit); - }); - } -}); - -// Extends view manager -openerp.base.ViewManagerRoot = openerp.base.Controller.extend({ -}); - -// Extends view manager -openerp.base.ViewManagerUsedAsAMany2One = openerp.base.Controller.extend({ -}); - -/** - * Management interface between views and the collection of selected OpenERP - * records (represents the view's state?) - */ -openerp.base.DataGroup = openerp.base.Controller.extend({ - init: function(session) { - this._super(session, null); - }, -}); - -/** - * Management interface between views and the collection of selected OpenERP - * records (represents the view's state?) - */ -openerp.base.DataSet = openerp.base.Controller.extend({ - init: function(session, model) { - this._super(session); - this.model = model; - - this._fields = null; - - this._ids = []; - this._active_ids = null; - this._active_id_index = 0; - - this._sort = []; - this._domain = []; - this._context = {}; - }, - start: function() { - // TODO: fields_view_get fields selection? - this.rpc("/base/dataset/fields", {"model":this.model}, this.on_fields); - }, - on_fields: function(result) { - this._fields = result._fields; - this.on_ready(); - }, - - /** - * Fetch all the records selected by this DataSet, based on its domain - * and context. - * - * Fires the on_ids event. - * - * @param {Number} [offset=0] The index from which selected records should be returned - * @param {Number} [limit=null] The maximum number of records to return - * @returns itself - */ - fetch: function (offset, limit) { - offset = offset || 0; - limit = limit || null; - this.rpc('/base/dataset/find', { - model: this.model, - fields: this._fields, - domain: this._domain, - context: this._context, - sort: this._sort, - offset: offset, - limit: limit - }, _.bind(function (records) { - var data_records = _.map( - records, function (record) { - return new openerp.base.DataRecord( - this.session, this.model, - this._fields, record); - }, this); - - this.on_fetch(data_records, { - offset: offset, - limit: limit, - domain: this._domain, - context: this._context, - sort: this._sort - }); - }, this)); - return this; - }, - /** - * @event - * - * Fires after the DataSet fetched the records matching its internal ids selection - * - * @param {Array} records An array of the DataRecord fetched - * @param event The on_fetch event object - * @param {Number} event.offset the offset with which the original DataSet#fetch call was performed - * @param {Number} event.limit the limit set on the original DataSet#fetch call - * @param {Array} event.domain the domain set on the DataSet before DataSet#fetch was called - * @param {Object} event.context the context set on the DataSet before DataSet#fetch was called - * @param {Array} event.sort the sorting criteria used to get the ids - */ - on_fetch: function (records, event) { }, - - /** - * Fetch all the currently active records for this DataSet (records selected via DataSet#select) - * - * @returns itself - */ - active_ids: function () { - this.rpc('/base/dataset/get', { - ids: this.get_active_ids(), - model: this.model - }, _.bind(function (records) { - this.on_active_ids(_.map( - records, function (record) { - return new openerp.base.DataRecord( - this.session, this.model, - this._fields, record); - }, this)); - }, this)); - return this; - }, - /** - * @event - * - * Fires after the DataSet fetched the records matching its internal active ids selection - * - * @param {Array} records An array of the DataRecord fetched - */ - on_active_ids: function (records) { }, - - /** - * Fetches the current active record for this DataSet - * - * @returns itself - */ - active_id: function () { - this.rpc('/base/dataset/get', { - ids: [this.get_active_id()], - model: this.model - }, _.bind(function (records) { - var record = records[0]; - this.on_active_id( - record && new openerp.base.DataRecord( - this.session, this.model, - this._fields, record)); - }, this)); - return this; - }, - /** - * Fires after the DataSet fetched the record matching the current active record - * - * @param record the record matching the provided id, or null if there is no record for this id - */ - on_active_id: function (record) { - - }, - - /** - * Configures the DataSet - * - * @param options DataSet options - * @param {Array} options.domain the domain to assign to this DataSet for filtering - * @param {Object} options.context the context this DataSet should use during its calls - * @param {Array} options.sort the sorting criteria for this DataSet - * @returns itself - */ - set: function (options) { - if (options.domain) { - this._domain = _.clone(options.domain); - } - if (options.context) { - this._context = _.clone(options.context); - } - if (options.sort) { - this._sort = _.clone(options.sort); - } - return this; - }, - - /** - * Activates the previous id in the active sequence. If there is no previous id, wraps around to the last one - * @returns itself - */ - prev: function () { - this._active_id_index -= 1; - if (this._active_id_index < 0) { - this._active_id_index = this._active_ids.length - 1; - } - return this; - }, - /** - * Activates the next id in the active sequence. If there is no next id, wraps around to the first one - * @returns itself - */ - next: function () { - this._active_id_index += 1; - if (this._active_id_index >= this._active_ids.length) { - this._active_id_index = 0; - } - return this; - }, - - /** - * Sets active_ids by value: - * - * * Activates all ids part of the current selection - * * Sets active_id to be the first id of the selection - * - * @param {Array} ids the list of ids to activate - * @returns itself - */ - select: function (ids) { - this._active_ids = ids; - this._active_id_index = 0; - return this; - }, - /** - * Fetches the ids of the currently selected records, if any. - */ - get_active_ids: function () { - return this._active_ids; - }, - /** - * Sets the current active_id by value - * - * If there are no active_ids selected, selects the provided id as the sole active_id - * - * If there are ids selected and the provided id is not in them, raise an error - * - * @param {Object} id the id to activate - * @returns itself - */ - activate: function (id) { - if(!this._active_ids) { - this._active_ids = [id]; - this._active_id_index = 0; - } else { - var index = _.indexOf(this._active_ids, id); - if (index == -1) { - throw new Error( - "Could not find id " + id + - " in array [" + this._active_ids.join(', ') + "]"); - } - this._active_id_index = index; - } - return this; - }, - /** - * Fetches the id of the current active record, if any. - * - * @returns record? record id or null - */ - get_active_id: function () { - if (!this._active_ids) { - return null; - } - return this._active_ids[this._active_id_index]; - } -}); - -openerp.base.DataRecord = openerp.base.Controller.extend({ - init: function(session, model, fields, values) { - this._super(session, null); - this.model = model; - this.id = values.id || null; - this.fields = fields; - this.values = values; - }, - on_change: function() { - }, - on_reload: function() { - } -}); - -/** - * Base class for widgets. Handle rendering (based on a QWeb template), identifier - * generation, parenting and destruction of the widget. - */ -openerp.base.BaseWidget = openerp.base.Controller.extend({ - /** - * The name of the QWeb template that will be used for rendering. Must be redifined - * in subclasses or the render() method can not be used. - * - * @type string - */ - template: null, - /** - * The prefix used to generate an id automatically. Should be redifined in subclasses. - * If it is not defined, a default identifier will be used. - * - * @type string - */ - identifier_prefix: 'generic-identifier', - /** - * Contructor. Also initialize the identifier. - * - * @params {openerp.base.search.BaseWidget} parent The parent widget. - */ - init: function (parent) { - this.children = []; - this.parent = null; - this.set_parent(parent); - this.make_id(this.identifier_prefix); - }, - /** - * Sets and returns a globally unique identifier for the widget. - * - * If a prefix is appended, the identifier will be appended to it. - * - * @params sections prefix sections, empty/falsy sections will be removed - */ - make_id: function () { - this.element_id = _.uniqueId(_.toArray(arguments).join('_')); - return this.element_id; - }, - /** - * "Starts" the widgets. Called at the end of the rendering, this allows - * to get a jQuery object referring to the DOM ($element attribute). - */ - start: function () { - this._super(); - var tmp = document.getElementById(this.element_id) - this.$element = tmp ? $(tmp) : null; - }, - /** - * "Stops" the widgets. Called when the view destroys itself, this - * lets the widgets clean up after themselves. - */ - stop: function () { - var tmp_children = this.children; - this.children = []; - _.each(tmp_children, function(x) { - x.stop(); - }); - if(this.$element != null) { - this.$element.remove(); - } - this.set_parent(null); - this._super(); - }, - /** - * Set the parent of this component, also unregister the previous parent if there - * was one. - * - * @param {openerp.base.BaseWidget} parent The new parent. - */ - set_parent: function(parent) { - if(this.parent) { - this.parent.children = _.without(this.parent.children, this); - } - this.parent = parent; - if(this.parent) { - parent.children.push(this); - } - }, - /** - * Render the widget. This.template must be defined. - * The content of the current object is passed as context to the template. - * - * @param {object} additional Additional context arguments to pass to the template. - */ - render: function (additional) { - return QWeb.render(this.template, _.extend({}, this, - additional != null ? additional : {})); - } -}); +openerp.addons.base.views = function(openerp) { openerp.base.SearchView = openerp.base.Controller.extend({ init: function(session, element_id, dataset, view_id, defaults) { @@ -1238,630 +742,6 @@ openerp.base.search.ManyToManyField = openerp.base.search.IntegerField.extend({ // TODO: .related_columns (Array), .context, .domain }); -openerp.base.FormView = openerp.base.Controller.extend({ - init: function(session, element_id, dataset, view_id) { - this._super(session, element_id); - this.dataset = dataset; - this.model = dataset.model; - this.view_id = view_id; - this.fields_views = {}; - this.widgets = {}; - this.widgets_counter = 0; - this.fields = {}; - this.datarecord = {}; - this.ready = false; - }, - start: function() { - //this.log('Starting FormView '+this.model+this.view_id) - this.rpc("/base/formview/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.log(this.fields_view); - - var frame = new openerp.base.WidgetFrame(this, this.fields_view.arch); - - this.$element.html(QWeb.render("FormView", { "frame": frame, "view": this })); - _.each(this.widgets, function(w) { - w.start(); - }); - this.$element.find('button.form_save').click(this.do_save); - - this.dataset.on_active_id.add(this.on_record_loaded); - }, - on_record_loaded: function(record) { - this.datarecord = record; - for (var f in this.fields) { - this.fields[f].set_value(this.datarecord.values[f]); - } - this.on_form_changed(); - this.ready = true; - }, - on_form_changed: function(widget) { - for (var w in this.widgets) { - w = this.widgets[w]; - w.process_attrs(); - w.update_dom(); - } - if (widget) { - // TODO: check if and trigger - // if (onchange for this field) { - // this.ready = false; - // rpc - process.callback ( this.ready = true; ) - // } - } - }, - do_save: function() { - if (!this.ready) { - return false; - } - var invalid = false; - var values = {}; - for (var f in this.fields) { - f = this.fields[f]; - if (f.invalid) { - invalid = true; - } else { - values[f.name] = f.value; - } - } - if (invalid) { - this.on_invalid(); - } else { - console.log("Save form", values); - // TODO: save values via datarecord - // rpc - save.callbacl on_saved - } - }, - on_invalid: function() { - }, - on_saved: function() { - // Check response for exceptions, display error - } -}); - -openerp.base.ListView = openerp.base.Controller.extend({ - init: function(session, element_id, dataset, view_id) { - this._super(session, element_id); - this.dataset = dataset; - this.model = dataset.model; - this.view_id = view_id; - this.name = ""; - - this.cols = []; - - this.$table = null; - this.colnames = []; - this.colmodel = []; - - this.event_loading = false; // TODO in the future prevent abusive click by masking - }, - start: function() { - //this.log('Starting ListView '+this.model+this.view_id) - this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); - }, - on_loaded: function(data) { - this.fields_view = data.fields_view; - //this.log(this.fields_view); - this.name = "" + this.fields_view.arch.attrs.string; - this.$element.html(QWeb.render("ListView", {"fields_view": this.fields_view})); - this.$table = this.$element.find("table"); - this.cols = []; - this.colnames = []; - this.colmodel = []; - // TODO uss a object for each col, fill it with view and fallback to dataset.model_field - var tree = this.fields_view.arch.children; - for(var i = 0; i < tree.length; i++) { - var col = tree[i]; - if(col.tag == "field") { - this.cols.push(col.attrs.name); - this.colnames.push(col.attrs.name); - this.colmodel.push({ name: col.attrs.name, index: col.attrs.name }); - } - } - this.dataset.fields = this.cols; - this.dataset.on_fetch.add(this.do_fill_table); - - var width = this.$element.width(); - this.$table.jqGrid({ - datatype: "local", - height: "100%", - rowNum: 100, - //rowList: [10,20,30], - colNames: this.colnames, - colModel: this.colmodel, - //pager: "#plist47", - viewrecords: true, - caption: this.name - }).setGridWidth(width); - - var self = this; - $(window).bind('resize', function() { - self.$element.children().hide(); - self.$table.setGridWidth(self.$element.width()); - self.$element.children().show(); - }).trigger('resize'); - }, - do_fill_table: function(records) { - this.log("do_fill_table"); - - this.$table - .clearGridData() - .addRowData('id', _.map(records, function (record) { - return record.values; - })); - - } -}); - -openerp.base.TreeView = openerp.base.Controller.extend({ -}); - - - -openerp.base.Widget = openerp.base.Controller.extend({ - // TODO Change this to init: function(view, node) { and use view.session and a new element_id for the super - // it means that widgets are special controllers - init: function(view, node) { - this.view = view; - this.node = node; - this.attrs = eval('(' + (this.node.attrs.attrs || '{}') + ')'); - this.type = this.type || node.tag; - this.element_name = this.element_name || this.type; - this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_"); - - this._super(this.view.session, this.element_id); - - this.view.widgets[this.element_id] = this; - this.children = node.children; - this.colspan = parseInt(node.attrs.colspan || 1); - this.template = "Widget"; - - this.string = this.string || node.attrs.string; - this.help = this.help || node.attrs.help; - this.invisible = (node.attrs.invisible == '1'); - }, - start: function() { - this.$element = $('#' + this.element_id); - }, - process_attrs: function() { - for (var a in this.attrs) { - this[a] = this.eval_attrs(this.attrs[a]); - } - }, - eval_attrs: function(expr) { - var stack = []; - for (var i = 0; i < expr.length; i++) { - var ex = expr[i]; - if (ex.length == 1) { - stack.push(ex[0]); - continue; - } - - var field = this.view.fields[ex[0]].value; - var op = ex[1]; - var val = ex[2]; - - switch (op.toLowerCase()) { - case '=': - case '==': - stack.push(field == val); - break; - case '!=': - case '<>': - stack.push(field != val); - break; - case '<': - stack.push(field < val); - break; - case '>': - stack.push(field > val); - break; - case '<=': - stack.push(field <= val); - break; - case '>=': - stack.push(field >= val); - break; - case 'in': - stack.push(_.indexOf(val, field) > -1); - break; - case 'not in': - stack.push(_.indexOf(val, field) == -1); - break; - default: - this.log("Unsupported operator in attrs :", op); - } - } - - for (var j = stack.length-1; j >- 1; j--) { - switch (stack[j]) { - case '|': - var result = stack[j + 1] || stack[j + 2]; - stack.splice(j, 3, result); - break; - case '&': - var result = stack[j + 1] && stack[j + 2]; - stack.splice(j, 3, result); - break; - } - } - return _.indexOf(stack, false) == -1; - }, - update_dom: function() { - this.$element.toggle(!this.invisible); - }, - render: function() { - var template = this.template; - return QWeb.render(template, { "widget": this }); - } -}); - -openerp.base.WidgetFrame = openerp.base.Widget.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "WidgetFrame"; - this.columns = node.attrs.col || 4; - this.x = 0; - this.y = 0; - this.table = []; - this.add_row(); - for (var i = 0; i < node.children.length; i++) { - var n = node.children[i]; - if (n.tag == "newline") { - this.add_row(); - } else { - this.handle_node(n); - } - } - this.set_row_cells_with(this.table[this.table.length - 1]); - }, - add_row: function(){ - if (this.table.length) { - this.set_row_cells_with(this.table[this.table.length - 1]); - } - var row = []; - this.table.push(row); - this.x = 0; - this.y += 1; - return row; - }, - set_row_cells_with: function(row) { - for (var i = 0; i < row.length; i++) { - var w = row[i]; - if (w.is_field_label) { - w.width = "1%"; - if (row[i + 1]) { - row[i + 1].width = Math.round((100 / this.columns) * (w.colspan + 1) - 1) + '%'; - } - } else if (w.width === undefined) { - w.width = Math.round((100 / this.columns) * w.colspan) + '%'; - } - } - }, - handle_node: function(n) { - var type = this.view.fields_view.fields[n.attrs.name] || {}; - var widget_type = n.attrs.widget || type.type || n.tag; - if (openerp.base.widgets[widget_type]) { - var widget = new openerp.base.widgets[widget_type](this.view, n); - if (n.tag == 'field' && n.attrs.nolabel != '1') { - var label = new openerp.base.widgets['label'](this.view, n); - label["for"] = widget; - this.add_widget(label); - } - this.add_widget(widget); - } else { - this.log("Unhandled widget type : " + widget_type, n); - } - }, - add_widget: function(w) { - if (!w.invisible) { - var current_row = this.table[this.table.length - 1]; - if (current_row.length && (this.x + w.colspan) > this.columns) { - current_row = this.add_row(); - } - current_row.push(w); - this.x += w.colspan; - } - return w; - } -}); - -openerp.base.WidgetNotebook = openerp.base.Widget.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "WidgetNotebook"; - this.pages = []; - for (var i = 0; i < node.children.length; i++) { - var n = node.children[i]; - if (n.tag == "page") { - var page = new openerp.base.WidgetFrame(this.view, n); - this.pages.push(page); - } - } - }, - start: function() { - this._super.apply(this, arguments); - this.$element.tabs(); - } -}); - -openerp.base.WidgetSeparator = openerp.base.Widget.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "WidgetSeparator"; - } -}); - -openerp.base.WidgetButton = openerp.base.Widget.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "WidgetButton"; - } -}); - -openerp.base.WidgetLabel = openerp.base.Widget.extend({ - init: function(view, node) { - this.is_field_label = true; - this.element_name = 'label_' + node.attrs.name; - - this._super(view, node); - - this.template = "WidgetLabel"; - this.colspan = 1; - }, - render: function () { - if (this['for'] && this.type !== 'label') { - return QWeb.render(this.template, {widget: this['for']}); - } - // Actual label widgets should not have a false and have type label - return QWeb.render(this.template, {widget: this}); - } -}); - -openerp.base.Field = openerp.base.Widget.extend({ - init: function(view, node) { - this.name = node.attrs.name; - this.value = undefined; - view.fields[this.name] = this; - this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type; - this.element_name = "field_" + this.name + "_" + this.type; - - this._super(view, node); - - if (node.attrs.nolabel != '1' && this.colspan > 1) { - this.colspan--; - } - // this.datarecord = this.view.datarecord ?? - this.field = view.fields_view.fields[node.attrs.name]; - this.string = node.attrs.string || this.field.string; - this.help = node.attrs.help || this.field.help; - this.nolabel = (node.attrs.nolabel == '1'); - this.readonly = (node.attrs.readonly == '1'); - this.required = (node.attrs.required == '1'); - this.invalid = false; - }, - set_value: function(value) { - this.value = value; - }, - get_value: function(value) { - return value; - }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.toggleClass('disabled', this.readonly); - this.$element.toggleClass('required', this.required); - }, - on_ui_change: function() { - this.view.on_form_changed(this); - } -}); - -openerp.base.FieldChar = openerp.base.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldChar"; - }, - start: function() { - this._super.apply(this, arguments); - this.$element.find('input').change(this.on_ui_change); - }, - set_value: function(value) { - this._super.apply(this, arguments); - if (value != null && value !== false) { - this.$element.find('input').val(value); - } - }, - get_value: function() { - }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('input').attr({ - 'disabled' : this.readonly, - 'required' : this.required - }); - }, - on_ui_change: function() { - this.value = this.$element.find('input').val(); - this.invalid = this.required && this.value == ""; - this._super.apply(this, arguments); - } -}); - -openerp.base.FieldEmail = openerp.base.FieldChar.extend({ -}); - -openerp.base.FieldUrl = openerp.base.FieldChar.extend({ -}); - -openerp.base.FieldFloat = openerp.base.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldChar"; - }, - start: function() { - this._super.apply(this, arguments); - this.$element.find('input').change(this.on_ui_change); - }, - set_value: function(value) { - this._super.apply(this, arguments); - if (value != null && value !== false) { - this.$element.find('input').val(value.toFixed(2)); - } - }, - get_value: function() { - }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('input').attr({ - 'disabled' : this.readonly, - 'required' : this.required - }); - }, - on_ui_change: function() { - this.value = this.$element.find('input').val(); - this.invalid = this.required && this.value == ""; - this._super.apply(this, arguments); - } -}); - -openerp.base.FieldText = openerp.base.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldText"; - }, - set_value: function(value) { - this._super.apply(this, arguments); - if (value != null && value !== false) { - this.$element.find('textarea').val(value); - } - }, - get_value: function() { - return this.$element.find('textarea').val(); - } -}); - -openerp.base.FieldBoolean = openerp.base.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldBoolean"; - }, - set_value: function(value) { - this._super.apply(this, arguments); - this.$element.find('input')[0].checked = value; - }, - get_value: function() { - this.$element.find('input')[0].checked; - } -}); - -openerp.base.FieldDate = openerp.base.FieldChar.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldDate"; - } -}); - -openerp.base.FieldDatetime = openerp.base.FieldChar.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldDatetime"; - } -}); - -openerp.base.FieldTextXml = openerp.base.Field.extend({ -// to replace view editor -}); - -openerp.base.FieldSelection = openerp.base.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldSelection"; - }, - set_value: function(value) { - this._super.apply(this, arguments); - if (value != null && value !== false) { - this.$element.find('select').val(value); - } - }, - get_value: function() { - return this.$element.find('select').val(); - } -}); - -openerp.base.FieldMany2One = openerp.base.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldMany2One"; - } -}); - -openerp.base.FieldOne2Many = openerp.base.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldOne2Many"; - } -}); - -openerp.base.FieldMany2Many = openerp.base.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldMany2Many"; - } -}); - -openerp.base.FieldReference = openerp.base.Field.extend({ - init: function(view, node) { - this._super(view, node); - this.template = "FieldReference"; - } -}); - -openerp.base.widgets = { - 'group' : openerp.base.WidgetFrame, - 'notebook' : openerp.base.WidgetNotebook, - 'separator' : openerp.base.WidgetSeparator, - 'label' : openerp.base.WidgetLabel, - 'char' : openerp.base.FieldChar, - 'email' : openerp.base.FieldEmail, - 'url' : openerp.base.FieldUrl, - 'text' : openerp.base.FieldText, - 'date' : openerp.base.FieldDate, - 'datetime' : openerp.base.FieldDatetime, - 'selection' : openerp.base.FieldSelection, - 'many2one' : openerp.base.FieldMany2One, - 'many2many' : openerp.base.FieldMany2Many, - 'one2many' : openerp.base.FieldOne2Many, - 'one2many_list' : openerp.base.FieldOne2Many, - 'reference' : openerp.base.FieldReference, - 'boolean' : openerp.base.FieldBoolean, - 'float' : openerp.base.FieldFloat, - 'button' : openerp.base.WidgetButton }; -openerp.base.CalendarView = openerp.base.Controller.extend({ -// Dhtmlx scheduler ? -}); - -openerp.base.GanttView = openerp.base.Controller.extend({ -// Dhtmlx gantt ? -}); - -openerp.base.DiagramView = openerp.base.Controller.extend({ -// -}); - -openerp.base.GraphView = openerp.base.Controller.extend({ -}); - -openerp.base.ProcessView = openerp.base.Controller.extend({ -}); - -openerp.base.HelpView = openerp.base.Controller.extend({ -}); - -}; - -// DEBUG_RPC:rpc.request:('execute', 'addons-dsh-l10n_us', 1, '*', ('ir.filters', 'get_filters', u'res.partner')) // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/tests.js b/addons/base/static/src/js/tests.js new file mode 100644 index 00000000000..39c8f213fe6 --- /dev/null +++ b/addons/base/static/src/js/tests.js @@ -0,0 +1,351 @@ +/** + * @function + * Defines a module scope (which lasts until the next call to module). + * + * This module scopes implies setup and teardown callbacks running for each test. + * + * @param {String} name the name of the module + * @param {Object} [lifecycle] callbacks to run before and after each test of the module + * @param {Function} lifecycle.setup function running before each test of this module + * @param {Function} lifecycle.teardown function running after each test of this module + */ +var module; +/** + * @function + * Defines a given test to run. Runs all the assertions present in the test + * + * @param {String} name the name of the test + * @param {Number} [expected] number of assertions expected to run in this test (useful for asynchronous tests) + * @param {Function} test the testing code to run, holding a sequence of assertions (at least one) + */ +var test; +/** + * @function + * Defines an asynchronous test: equivalent to calling stop() at the start of + * a normal test(). + * + * The test code needs to restart the test runner via start() + * + * @param {String} name the name of the test + * @param {Number} [expected] number of assertions expected to run in this test (useful for asynchronous tests) + * @param {Function} test the testing code to run, holding a sequence of assertions (at least one) + */ +var asyncTest; +/** + * @function + * The most basic boolean assertion (~assertTrue or assert). + * + * Passes if its argument is truthy + * + * @param {Boolean} state an arbitrary expression, evaluated in a boolean context + * @param {String} [message] the message to output with the assertion result + */ +var ok; +/** + * @function + * Equality assertion (~assertEqual) + * + * Passes if both arguments are equal (via ==) + * + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var equal; +/** + * @function + * Inequality assertion (~assertNotEqual) + * + * Passes if the arguments are different (via !=) + * + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var notEqual; +/** + * @function + * Recursive equality assertion. + * + * Works on primitive types using === and traversing through + * Objects and Arrays as well checking their components + * + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var deepEqual; +/** + * @function + * Recursive inequality assertion. + * + * Works on primitive types using !== and traversing through + * Objects and Arrays as well checking their components + * + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var notDeepEqual; +/** + * @function + * Strict equality assertion (~assertEqual) + * + * Passes if both arguments are identical (via ===) + * + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var strictEqual; +/** + * @function + * Strict inequality assertion (~assertNotEqual) + * + * Passes if both arguments are identical (via !==) + * + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var notStrictEqual; +/** + * @function + * Passes if the provided block raised an exception. + * + * The expect argument can be provided to perform further assertion checks on the exception itself: + * * If it's a RegExp test the exception against the regexp (message?) + * * If it's a constructor, check if the exception is an instance of it + * * If it's an other type of function, call it with the exception as first parameter + * - If the function returns true, the assertion validates + * - Otherwise it fails + * + * @param {Function} block function which should raise an exception when called + * @param {Object} [expect] a RegExp, a constructor or a Function + * @param {String} [message] message output with the assertion result + */ +var raises; +/** + * @function + * Starts running the test runner again from the point where it was + * stopped. + * + * Used to resume testing after a callback. + */ +var start; +/** + * @function + * Stops the test runner in order to wait for an asynchronous test to run + * + * @param {Number} [timeout] fails the test after the timeout triggers, only for debugging tests + */ +var stop; + +var Session = function () { + return { + rpc: function (_url, params, on_success) { + setTimeout(on_success); + } + }; +}; +$(document).ready(function () { + var openerp; + module("ids_callback", { + setup: function () { + openerp = window.openerp.init(); + } + }); + asyncTest("Baseline event attributes", 6, function () { + var dataset = new openerp.base.DataSet( + new Session()); + dataset.on_fetch.add(function (records, event) { + deepEqual(records, [], 'No records returned'); + equal(event.offset, 0, 'No offset set in call'); + equal(event.limit, null, 'No limit set in call'); + deepEqual(event.domain, [], 'No domain on the dataset'); + deepEqual(event.context, {}, 'No context on the dataset'); + deepEqual(event.sort, [], 'The dataset is not sorted'); + start(); + }); + dataset.fetch(); + }); + asyncTest("Offset and limit", 2, function () { + var dataset = new openerp.base.DataSet( + new Session()); + dataset.on_fetch.add(function (records, event) { + equal(event.offset, 20); + equal(event.limit, 42); + start(); + }); + dataset.fetch(20, 42); + }); + asyncTest("Domain and context propagation", 3, function () { + var dataset = new openerp.base.DataSet( + new Session()); + var domain_value = [['foo', '=', 'bar']]; + var context_value= {active_id:3, active_ids:42}; + var sort_value = ['foo']; + dataset.on_fetch.add(function (records, event) { + deepEqual(event.domain, domain_value); + deepEqual(event.context, context_value); + deepEqual(event.sort, sort_value); + start(); + }); + dataset.set({ + domain: domain_value, + context: context_value, + sort: sort_value + }); + dataset.fetch(); + }); + asyncTest("Data records", function () { + var dataset = new openerp.base.DataSet({ + rpc: function (url, _params, on_success) { + equal('/base/dataset/find', url); + _.delay(on_success, 0, [ + {id: 1, sequence: 3, name: "dummy", age: 42}, + {id: 5, sequence: 7, name: "whee", age: 55} + ]); + } + }); + dataset.on_fetch.add(function (records) { + equal(records.length, 2, "I loaded two virtual records"); + var d1 = records[0], + d2 = records[1]; + ok(d1 instanceof openerp.base.DataRecord); + ok(d2 instanceof openerp.base.DataRecord); + start(); + }); + dataset.fetch(); + }); + + var dataset; + module("set", { + setup: function () { + var openerp = window.openerp.init(); + dataset = new openerp.base.DataSet(); + } + }); + test('Basic properties setting', function () { + var domain_value = [['foo', '=', 'bar']]; + var result = dataset.set({ + domain: domain_value + }); + ok(dataset === result); + deepEqual(domain_value, dataset._domain); + }); + test("Ensure changes don't stick", function () { + var domain = [['foo', '=', 'bar']]; + dataset.set({ + domain: domain + }); + domain.pop(); + deepEqual([['foo', '=', 'bar']], dataset._domain); + }); + + module('ids_activation', { + setup: function () { + var openerp = window.openerp.init(); + dataset = new openerp.base.DataSet(); + } + }); + test('activate id', function () { + dataset.activate(1); + deepEqual(dataset.get_active_ids(), [1]); + equal(dataset.get_active_id(), 1); + }); + test('set active_ids', function () { + dataset.select([1, 2, 3]); + deepEqual(dataset.get_active_ids(), [1, 2, 3], + "selecting an ids range"); + equal(dataset.get_active_id(), 1); + }); + test('activate incorrect id', function () { + dataset.select([1, 2, 3]); + raises(function () { dataset.activate(42); }, + "Activating an id not present in the selection is an error"); + }); + test('reset active id on set active ids', function () { + dataset.select([1, 2, 3]).activate(3).select([1, 2, 3]); + equal(dataset.get_active_id(), 1, + "selecting an ids range resets the active id"); + }); + + module('active_id_iteration', { + setup: function () { + var openerp = window.openerp.init(); + dataset = new openerp.base.DataSet(); + dataset.select([1, 2, 3]); + } + }); + test('step forward', function () { + dataset.activate(1); + dataset.next(); + equal(dataset.get_active_id(), 2); + }); + test('wraparound forward', function () { + dataset.activate(3); + dataset.next(); + equal(dataset.get_active_id(), 1); + }); + test('step back', function () { + dataset.activate(3); + dataset.prev(); + equal(dataset.get_active_id(), 2); + }); + test('wraparound back', function () { + dataset.activate(1); + dataset.prev(); + equal(dataset.get_active_id(), 3); + }); + + var ResponseAssertSession = function (response_ids) { + return { + rpc: function (url, params, on_success) { + equal(url, '/base/dataset/get'); + deepEqual(params.ids, response_ids); + _.delay(on_success, 0, _.map( + params.ids, function (id) { + return {id: id, sequence: id, name: 'foo'+id}; + } + )); + } + }; + }; + + module('active_ids', { + setup: function () { + openerp = window.openerp.init(); + } + }); + asyncTest('Get pre-set active_ids', 6, function () { + var dataset = new openerp.base.DataSet( + new ResponseAssertSession([1, 2, 3])); + dataset.select([1, 2, 3]); + dataset.on_active_ids.add(function (data_records) { + equal(data_records.length, 3); + equal(data_records[0].values.id, 1); + equal(data_records[1].values.id, 2); + equal(data_records[2].values.id, 3); + start(); + }); + dataset.active_ids(); + }); + + module('active_id', { + setup: function () { + openerp = window.openerp.init(); + } + }); + test('Get pre-set active_id', 3, function () { + var dataset = new openerp.base.DataSet( + new ResponseAssertSession([42])); + stop(500); + dataset.select([1, 2, 3, 42]).activate(42); + dataset.on_active_id.add(function (data_record) { + equal(data_record.values.id, 42); + start(); + }); + dataset.active_id(); + }); +}); diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js new file mode 100644 index 00000000000..155e8811e17 --- /dev/null +++ b/addons/base/static/src/js/views.js @@ -0,0 +1,257 @@ +/*--------------------------------------------------------- + * OpenERP base library + *---------------------------------------------------------*/ + +openerp.addons.base.views = function(openerp) { + +// process all kind of actions +openerp.base.ActionManager = openerp.base.Controller.extend({ + init: function(session, element_id) { + this._super(session, element_id); + this.action = null; + this.viewmanager = null; + }, + /** + * Process an action + * Supported actions: act_window + */ + do_action: function(action) { + // instantiate the right controllers by understanding the action + this.action = action; + if(action.type == "ir.actions.act_window") { + this.viewmanager = new openerp.base.ViewManager(this.session,this.element_id); + this.viewmanager.do_action_window(action); + this.viewmanager.start(); + } + } +}); + +// This will be ViewManager Abstract/Common +openerp.base.ViewManager = openerp.base.Controller.extend({ + init: function(session, element_id) { + this._super(session, element_id); + this.action = null; + this.dataset = null; + this.searchview_id = false; + this.searchview = null; + this.search_visible = true; + // this.views = { "list": { "view_id":1234, "controller": instance} } + this.views = {}; + }, + start: function() { + }, + on_mode_switch: function(view_type) { + for (var i in this.views) { + this.views[i].controller.$element.toggle(i === view_type); + } + }, + /** + * Extract search view defaults from the current action's context. + * + * These defaults are of the form {search_default_*: value} + * + * @returns {Object} a clean defaults mapping of {field_name: value} + */ + search_defaults: function () { + var defaults = {}; + _.each(this.action.context, function (value, key) { + var match = /^search_default_(.*)$/.exec(key); + if (match) { + defaults[match[1]] = value; + } + }); + return defaults; + }, + do_action_window: function(action) { + var self = this; + var prefix_id = "#" + this.element_id; + this.action = action; + this.dataset = new openerp.base.DataSet(this.session, action.res_model); + this.dataset.start(); + + this.$element.html(QWeb.render("ViewManager", {"prefix": this.element_id, views: action.views})); + + this.searchview_id = false; + if(this.search_visible && action.search_view_id) { + this.searchview_id = action.search_view_id[0]; + var searchview = this.searchview = new openerp.base.SearchView( + this.session, this.element_id + "_search", + this.dataset, this.searchview_id, + this.search_defaults()); + searchview.on_search.add(this.do_search); + searchview.start(); + + if (action['auto_search']) { + searchview.on_loaded.add_last( + searchview.do_search); + } + } + for(var i = 0; i < action.views.length; i++) { + var view_id, controller; + view_id = action.views[i][0]; + if(action.views[i][1] == "tree") { + controller = new openerp.base.ListView(this.session, this.element_id + "_view_tree", this.dataset, view_id); + controller.start(); + this.views.tree = { view_id: view_id, controller: controller }; + this.$element.find(prefix_id + "_button_tree").bind('click',function(){ + self.on_mode_switch("tree"); + }); + } else if(action.views[i][1] == "form") { + controller = new openerp.base.FormView(this.session, this.element_id + "_view_form", this.dataset, view_id); + controller.start(); + this.views.form = { view_id: view_id, controller: controller }; + this.$element.find(prefix_id + "_button_form").bind('click',function(){ + self.on_mode_switch("form"); + }); + } + } + // switch to the first one in sequence + this.on_mode_switch("tree"); + }, + // create when root, also add to parent when o2m + on_create: function() { + }, + on_remove: function() { + }, + on_edit: function() { + }, + do_search: function (domains, contexts, groupbys) { + var self = this; + this.rpc('/base/session/eval_domain_and_context', { + domains: domains, + contexts: contexts, + group_by_seq: groupbys + }, function (results) { + // TODO: handle non-empty results.group_by with read_group + self.dataset.set({ + context: results.context, + domain: results.domain + }).fetch(0, self.action.limit); + }); + } +}); + +// Extends view manager +openerp.base.ViewManagerRoot = openerp.base.Controller.extend({ +}); + +// Extends view manager +openerp.base.ViewManagerUsedAsAMany2One = openerp.base.Controller.extend({ +}); + +/** + * Base class for widgets. Handle rendering (based on a QWeb template), identifier + * generation, parenting and destruction of the widget. + */ +openerp.base.BaseWidget = openerp.base.Controller.extend({ + /** + * The name of the QWeb template that will be used for rendering. Must be redifined + * in subclasses or the render() method can not be used. + * + * @type string + */ + template: null, + /** + * The prefix used to generate an id automatically. Should be redifined in subclasses. + * If it is not defined, a default identifier will be used. + * + * @type string + */ + identifier_prefix: 'generic-identifier', + /** + * Contructor. Also initialize the identifier. + * + * @params {openerp.base.search.BaseWidget} parent The parent widget. + */ + init: function (parent) { + this.children = []; + this.parent = null; + this.set_parent(parent); + this.make_id(this.identifier_prefix); + }, + /** + * Sets and returns a globally unique identifier for the widget. + * + * If a prefix is appended, the identifier will be appended to it. + * + * @params sections prefix sections, empty/falsy sections will be removed + */ + make_id: function () { + this.element_id = _.uniqueId(_.toArray(arguments).join('_')); + return this.element_id; + }, + /** + * "Starts" the widgets. Called at the end of the rendering, this allows + * to get a jQuery object referring to the DOM ($element attribute). + */ + start: function () { + this._super(); + var tmp = document.getElementById(this.element_id) + this.$element = tmp ? $(tmp) : null; + }, + /** + * "Stops" the widgets. Called when the view destroys itself, this + * lets the widgets clean up after themselves. + */ + stop: function () { + var tmp_children = this.children; + this.children = []; + _.each(tmp_children, function(x) { + x.stop(); + }); + if(this.$element != null) { + this.$element.remove(); + } + this.set_parent(null); + this._super(); + }, + /** + * Set the parent of this component, also unregister the previous parent if there + * was one. + * + * @param {openerp.base.BaseWidget} parent The new parent. + */ + set_parent: function(parent) { + if(this.parent) { + this.parent.children = _.without(this.parent.children, this); + } + this.parent = parent; + if(this.parent) { + parent.children.push(this); + } + }, + /** + * Render the widget. This.template must be defined. + * The content of the current object is passed as context to the template. + * + * @param {object} additional Additional context arguments to pass to the template. + */ + render: function (additional) { + return QWeb.render(this.template, _.extend({}, this, + additional != null ? additional : {})); + } +}); +openerp.base.CalendarView = openerp.base.Controller.extend({ +// Dhtmlx scheduler ? +}); + +openerp.base.GanttView = openerp.base.Controller.extend({ +// Dhtmlx gantt ? +}); + +openerp.base.DiagramView = openerp.base.Controller.extend({ +// +}); + +openerp.base.GraphView = openerp.base.Controller.extend({ +}); + +openerp.base.ProcessView = openerp.base.Controller.extend({ +}); + +openerp.base.HelpView = openerp.base.Controller.extend({ +}); + +}; + +// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: