[MERGE] Merged latest trunk.
bzr revid: vta@openerp.com-20120802155941-oomnsjzi43fedpdc
This commit is contained in:
commit
adaa03a247
|
@ -53,7 +53,6 @@ This module provides the core of the OpenERP Web Client.
|
|||
"static/src/js/view_list.js",
|
||||
"static/src/js/view_list_editable.js",
|
||||
"static/src/js/view_tree.js",
|
||||
"static/src/js/view_editor.js"
|
||||
],
|
||||
'css' : [
|
||||
"static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.8.16.custom.css",
|
||||
|
|
|
@ -2474,27 +2474,6 @@
|
|||
.openerp .oe_trad_field.touched {
|
||||
border: 1px solid green !important;
|
||||
}
|
||||
.openerp .oe_view_editor {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-left: -12px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
border-spacing: 0;
|
||||
}
|
||||
.openerp .oe_view_editor td {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
border: 1px solid #d8d8d8;
|
||||
cursor: pointer;
|
||||
font-size: 90%;
|
||||
}
|
||||
.openerp .oe_view_editor_field td {
|
||||
border: 0px !important;
|
||||
}
|
||||
.openerp .oe_view_editor tr:hover {
|
||||
background-color: #ecebf2;
|
||||
}
|
||||
.openerp .oe_layout_debugging .oe_form_group {
|
||||
outline: 2px dashed green;
|
||||
}
|
||||
|
|
|
@ -1929,25 +1929,6 @@ $sheet-max-width: 860px
|
|||
.oe_trad_field.touched
|
||||
border: 1px solid green !important
|
||||
// }}}
|
||||
// View Editor {{{
|
||||
.oe_view_editor
|
||||
width: 100%
|
||||
border-collapse: collapse
|
||||
margin-left: -12px
|
||||
width: 100%
|
||||
background-color: white
|
||||
border-spacing: 0
|
||||
td
|
||||
text-align: center
|
||||
white-space: nowrap
|
||||
border: 1px solid #D8D8D8
|
||||
cursor: pointer
|
||||
font-size: 90%
|
||||
.oe_view_editor_field td
|
||||
border: 0px !important
|
||||
.oe_view_editor tr:hover
|
||||
background-color: #ecebf2
|
||||
// }}}
|
||||
// Debugging stuff {{{
|
||||
.oe_layout_debugging
|
||||
.oe_form_group
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
* OpenERP Web web module split
|
||||
*---------------------------------------------------------*/
|
||||
openerp.web = function(session) {
|
||||
var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import","view_editor"];
|
||||
var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"];
|
||||
for(var i=0; i<files.length; i++) {
|
||||
if(openerp.web[files[i]]) {
|
||||
openerp.web[files[i]](session);
|
||||
|
|
|
@ -57,9 +57,7 @@ instance.web.Dialog = instance.web.Widget.extend({
|
|||
init: function (parent, options, content) {
|
||||
var self = this;
|
||||
this._super(parent);
|
||||
if (content) {
|
||||
this.$element = content instanceof $ ? content : $(content);
|
||||
}
|
||||
this.content_to_set = content;
|
||||
this.dialog_options = {
|
||||
modal: true,
|
||||
destroy_on_close: true,
|
||||
|
@ -83,11 +81,6 @@ instance.web.Dialog = instance.web.Widget.extend({
|
|||
if (options) {
|
||||
_.extend(this.dialog_options, options);
|
||||
}
|
||||
if (this.dialog_options.autoOpen) {
|
||||
this.open();
|
||||
} else {
|
||||
instance.web.dialog(this.$element, this.get_options());
|
||||
}
|
||||
},
|
||||
get_options: function(options) {
|
||||
var self = this,
|
||||
|
@ -116,31 +109,44 @@ instance.web.Dialog = instance.web.Widget.extend({
|
|||
} else if (val.slice(-1) == "%") {
|
||||
return Math.round(available_size / 100 * parseInt(val.slice(0, -1), 10));
|
||||
} else {
|
||||
return parseInt(val, 10);
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
},
|
||||
renderElement: function() {
|
||||
if (this.content_to_set) {
|
||||
this.setElement(this.content_to_set);
|
||||
} else if (this.template) {
|
||||
this._super();
|
||||
}
|
||||
},
|
||||
open: function(options) {
|
||||
// TODO fme: bind window on resize
|
||||
if (this.template) {
|
||||
this.$element.html(this.renderElement());
|
||||
}
|
||||
if (! this.dialog_inited)
|
||||
this.init_dialog();
|
||||
var o = this.get_options(options);
|
||||
instance.web.dialog(this.$element, o).dialog('open');
|
||||
instance.web.dialog(this.$element, o).dialog('open');
|
||||
if (o.height === 'auto' && o.max_height) {
|
||||
this.$element.css({ 'max-height': o.max_height, 'overflow-y': 'auto' });
|
||||
}
|
||||
return this;
|
||||
},
|
||||
init_dialog: function(options) {
|
||||
this.renderElement();
|
||||
var o = this.get_options(options);
|
||||
instance.web.dialog(this.$element, o);
|
||||
var res = this.start();
|
||||
this.dialog_inited = true;
|
||||
return res;
|
||||
},
|
||||
close: function() {
|
||||
this.$element.dialog('close');
|
||||
},
|
||||
on_close: function() {
|
||||
if (this.__tmp_dialog_destroying)
|
||||
return;
|
||||
if (this.__tmp_dialog_destroying)
|
||||
return;
|
||||
if (this.dialog_options.destroy_on_close) {
|
||||
this.__tmp_dialog_closing = true;
|
||||
this.__tmp_dialog_closing = true;
|
||||
this.destroy();
|
||||
this.__tmp_dialog_closing = undefined;
|
||||
this.__tmp_dialog_closing = undefined;
|
||||
}
|
||||
},
|
||||
on_resized: function() {
|
||||
|
@ -150,10 +156,10 @@ instance.web.Dialog = instance.web.Widget.extend({
|
|||
el.destroy();
|
||||
});
|
||||
if (! this.__tmp_dialog_closing) {
|
||||
this.__tmp_dialog_destroying = true;
|
||||
this.close();
|
||||
this.__tmp_dialog_destroying = undefined;
|
||||
}
|
||||
this.__tmp_dialog_destroying = true;
|
||||
this.close();
|
||||
this.__tmp_dialog_destroying = undefined;
|
||||
}
|
||||
if (! this.isDestroyed()) {
|
||||
this.$element.dialog('destroy');
|
||||
}
|
||||
|
@ -263,11 +269,11 @@ instance.web.Loading = instance.web.Widget.extend({
|
|||
|
||||
this.count += increment;
|
||||
if (this.count > 0) {
|
||||
if (instance.connection.debug) {
|
||||
this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count));
|
||||
} else {
|
||||
this.$element.text(_t("Loading"));
|
||||
}
|
||||
if (instance.connection.debug) {
|
||||
this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count));
|
||||
} else {
|
||||
this.$element.text(_t("Loading"));
|
||||
}
|
||||
this.$element.show();
|
||||
this.getParent().$element.addClass('oe_wait');
|
||||
} else {
|
||||
|
@ -496,7 +502,11 @@ instance.web.Login = instance.web.Widget.extend({
|
|||
this.has_local_storage = typeof(localStorage) != 'undefined';
|
||||
this.selected_db = null;
|
||||
this.selected_login = null;
|
||||
this.params = params;
|
||||
this.params = params || {};
|
||||
|
||||
if (this.params.login_successful) {
|
||||
this.on('login_successful', this, this.params.login_successful);
|
||||
}
|
||||
|
||||
if (this.has_local_storage && this.remember_credentials) {
|
||||
this.selected_db = localStorage.getItem('last_db_login_success');
|
||||
|
@ -513,7 +523,7 @@ instance.web.Login = instance.web.Widget.extend({
|
|||
self.do_action("database_manager");
|
||||
});
|
||||
return self.load_db_list().then(self.on_db_list_loaded).then(function() {
|
||||
if(self.params) {
|
||||
if (self.params.db) {
|
||||
self.do_login(self.params.db, self.params.login, self.params.password);
|
||||
}
|
||||
});
|
||||
|
@ -589,6 +599,12 @@ instance.web.Login = instance.web.Widget.extend({
|
|||
self.$(".oe_login_pane").fadeIn("fast");
|
||||
self.$element.addClass("oe_login_invalid");
|
||||
});
|
||||
},
|
||||
show: function () {
|
||||
this.$element.show();
|
||||
},
|
||||
hide: function () {
|
||||
this.$element.hide();
|
||||
}
|
||||
});
|
||||
instance.web.client_actions.add("login", "instance.web.Login");
|
||||
|
@ -880,12 +896,11 @@ instance.web.Client = instance.web.Widget.extend({
|
|||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
return instance.connection.session_bind(this.origin).then(function() {
|
||||
return instance.connection.session_bind(this.origin).pipe(function() {
|
||||
var $e = $(QWeb.render(self._template, {}));
|
||||
self.$element.replaceWith($e);
|
||||
self.$element = $e;
|
||||
self.replaceElement($e);
|
||||
self.bind_events();
|
||||
self.show_common();
|
||||
return self.show_common();
|
||||
});
|
||||
},
|
||||
bind_events: function() {
|
||||
|
@ -914,8 +929,10 @@ instance.web.Client = instance.web.Widget.extend({
|
|||
}
|
||||
}, 0);
|
||||
});
|
||||
instance.web.bus.on('click', this, function() {
|
||||
self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened');
|
||||
instance.web.bus.on('click', this, function(ev) {
|
||||
if (!$(ev.target).is('input[type=file]')) {
|
||||
self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened');
|
||||
}
|
||||
});
|
||||
},
|
||||
show_common: function() {
|
||||
|
|
|
@ -491,23 +491,19 @@ instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, {
|
|||
*
|
||||
* The semantics of this precisely replace closing over the method call.
|
||||
*
|
||||
* @param {String} method_name name of the method to invoke
|
||||
* @param {String|Function} method function or name of the method to invoke
|
||||
* @returns {Function} proxied method
|
||||
*/
|
||||
proxy: function (method_name) {
|
||||
proxy: function (method) {
|
||||
var self = this;
|
||||
return function () {
|
||||
return self[method_name].apply(self, arguments);
|
||||
var fn = (typeof method === 'string') ? self[method] : method;
|
||||
return fn.apply(self, arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
||||
/**
|
||||
* Tag name when creating a default $element.
|
||||
* @type string
|
||||
*/
|
||||
tagName: 'div',
|
||||
/**
|
||||
* Constructs the widget and sets its parent if a parent is given.
|
||||
*
|
||||
|
@ -517,14 +513,9 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
|||
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
|
||||
* When that widget is destroyed by calling destroy(), the current instance will be
|
||||
* destroyed too. Can be null.
|
||||
* @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
|
||||
* to bind the current Widget to an already existing part of the DOM, which is not compatible
|
||||
* with the DOM insertion methods provided by the current implementation of Widget. So
|
||||
* for new components this argument should not be provided any more.
|
||||
*/
|
||||
init: function(parent) {
|
||||
instance.web.CallbackEnabledMixin.init.call(this);
|
||||
this.$element = $(document.createElement(this.tagName));
|
||||
this.setParent(parent);
|
||||
},
|
||||
/**
|
||||
|
@ -534,7 +525,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
|||
_.each(this.getChildren(), function(el) {
|
||||
el.destroy();
|
||||
});
|
||||
if(this.$element != null) {
|
||||
if(this.$element) {
|
||||
this.$element.remove();
|
||||
}
|
||||
instance.web.PropertiesMixin.destroy.call(this);
|
||||
|
@ -613,6 +604,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
|||
* @returns {jQuery.Deferred}
|
||||
*/
|
||||
start: function() {
|
||||
return $.when();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -673,6 +665,12 @@ instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEn
|
|||
* That will kill the widget in a clean way and erase its content from the dom.
|
||||
*/
|
||||
instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
|
||||
// Backbone-ish API
|
||||
tagName: 'div',
|
||||
id: null,
|
||||
className: null,
|
||||
attributes: {},
|
||||
events: {},
|
||||
/**
|
||||
* The name of the QWeb template that will be used for rendering. Must be
|
||||
* redefined in subclasses or the default render() method can not be used.
|
||||
|
@ -689,13 +687,11 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
|
|||
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
|
||||
* When that widget is destroyed by calling destroy(), the current instance will be
|
||||
* destroyed too. Can be null.
|
||||
* @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
|
||||
* to bind the current Widget to an already existing part of the DOM, which is not compatible
|
||||
* with the DOM insertion methods provided by the current implementation of Widget. So
|
||||
* for new components this argument should not be provided any more.
|
||||
*/
|
||||
init: function(parent) {
|
||||
instance.web.WidgetMixin.init.call(this,parent);
|
||||
// FIXME: this should not be
|
||||
this.setElement(this._make_descriptive());
|
||||
this.session = instance.connection;
|
||||
},
|
||||
/**
|
||||
|
@ -704,20 +700,120 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
|
|||
* key that references `this`.
|
||||
*/
|
||||
renderElement: function() {
|
||||
var rendered = null;
|
||||
if (this.template)
|
||||
rendered = instance.web.qweb.render(this.template, {widget: this});
|
||||
if (_.str.trim(rendered)) {
|
||||
var elem = $(rendered);
|
||||
this.$element.replaceWith(elem);
|
||||
this.$element = elem;
|
||||
var $el;
|
||||
if (this.template) {
|
||||
$el = $(_.str.trim(instance.web.qweb.render(
|
||||
this.template, {widget: this})));
|
||||
} else {
|
||||
$el = this._make_descriptive();
|
||||
}
|
||||
this.replaceElement($el);
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-sets the widget's root element and replaces the old root element
|
||||
* (if any) by the new one in the DOM.
|
||||
*
|
||||
* @param {HTMLElement | jQuery} $el
|
||||
* @returns {*} this
|
||||
*/
|
||||
replaceElement: function ($el) {
|
||||
var $oldel = this.$element;
|
||||
this.setElement($el);
|
||||
if ($oldel && !$oldel.is(this.$element)) {
|
||||
$oldel.replaceWith(this.$element);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Shortcut for $element.find() like backbone
|
||||
* Re-sets the widget's root element (el/$el/$element).
|
||||
*
|
||||
* Includes:
|
||||
* * re-delegating events
|
||||
* * re-binding sub-elements
|
||||
* * if the widget already had a root element, replacing the pre-existing
|
||||
* element in the DOM
|
||||
*
|
||||
* @param {HTMLElement | jQuery} element new root element for the widget
|
||||
* @return {*} this
|
||||
*/
|
||||
"$": function() {
|
||||
return this.$element.find.apply(this.$element,arguments);
|
||||
setElement: function (element) {
|
||||
// NB: completely useless, as WidgetMixin#init creates a $element
|
||||
// always
|
||||
if (this.$element) {
|
||||
this.undelegateEvents();
|
||||
}
|
||||
|
||||
this.$element = (element instanceof $) ? element : $(element);
|
||||
this.el = this.$element[0];
|
||||
|
||||
this.delegateEvents();
|
||||
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Utility function to build small DOM elements.
|
||||
*
|
||||
* @param {String} tagName name of the DOM element to create
|
||||
* @param {Object} [attributes] map of DOM attributes to set on the element
|
||||
* @param {String} [content] HTML content to set on the element
|
||||
* @return {Element}
|
||||
*/
|
||||
make: function (tagName, attributes, content) {
|
||||
var el = document.createElement(tagName);
|
||||
if (!_.isEmpty(attributes)) {
|
||||
$(el).attr(attributes);
|
||||
}
|
||||
if (content) {
|
||||
$(el).html(content);
|
||||
}
|
||||
return el;
|
||||
},
|
||||
/**
|
||||
* Makes a potential root element from the declarative builder of the
|
||||
* widget
|
||||
*
|
||||
* @return {jQuery}
|
||||
* @private
|
||||
*/
|
||||
_make_descriptive: function () {
|
||||
var attrs = _.extend({}, this.attributes || {});
|
||||
if (this.id) { attrs.id = this.id; }
|
||||
if (this.className) { attrs['class'] = this.className; }
|
||||
return $(this.make(this.tagName, attrs));
|
||||
},
|
||||
delegateEvents: function () {
|
||||
var events = this.events;
|
||||
if (_.isEmpty(events)) { return; }
|
||||
|
||||
for(var key in events) {
|
||||
if (!events.hasOwnProperty(key)) { continue; }
|
||||
|
||||
var method = this.proxy(events[key]);
|
||||
|
||||
var match = /^(\S+)(\s+(.*))?$/.exec(key);
|
||||
var event = match[1];
|
||||
var selector = match[3];
|
||||
|
||||
event += '.widget_events';
|
||||
if (!selector) {
|
||||
this.$element.on(event, method);
|
||||
} else {
|
||||
this.$element.on(event, selector, method);
|
||||
}
|
||||
}
|
||||
},
|
||||
undelegateEvents: function () {
|
||||
this.$element.off('.widget_events');
|
||||
},
|
||||
/**
|
||||
* Shortcut for ``this.$element.find(selector)``
|
||||
*
|
||||
* @param {String} selector CSS selector, rooted in $el
|
||||
* @returns {jQuery} selector match
|
||||
*/
|
||||
$: function(selector) {
|
||||
return this.$element.find(selector);
|
||||
},
|
||||
/**
|
||||
* Informs the action manager to do an action. This supposes that
|
||||
|
@ -1039,7 +1135,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
uid: new py.float(this.uid),
|
||||
datetime: datetime,
|
||||
time: time,
|
||||
relativedelta: relativedelta
|
||||
relativedelta: relativedelta,
|
||||
current_date: date.today.__call__().strftime(['%Y-%m-%d'])
|
||||
};
|
||||
},
|
||||
/**
|
||||
|
@ -1280,7 +1377,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
processData: false
|
||||
}, url);
|
||||
if (this.synch)
|
||||
ajax.async = false;
|
||||
ajax.async = false;
|
||||
return $.ajax(ajax);
|
||||
},
|
||||
rpc_jsonp: function(url, payload) {
|
||||
|
@ -1299,7 +1396,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
|||
data: data
|
||||
}, url);
|
||||
if (this.synch)
|
||||
ajax.async = false;
|
||||
ajax.async = false;
|
||||
var payload_str = JSON.stringify(payload);
|
||||
var payload_url = $.param({r:payload_str});
|
||||
if(payload_url.length < 2000) {
|
||||
|
|
|
@ -19,15 +19,14 @@ instance.web.OldWidget = instance.web.Widget.extend({
|
|||
this._super(parent);
|
||||
this.element_id = element_id;
|
||||
this.element_id = this.element_id || _.uniqueId('widget-');
|
||||
|
||||
var tmp = document.getElementById(this.element_id);
|
||||
this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName));
|
||||
this.setElement(tmp || this._make_descriptive());
|
||||
},
|
||||
renderElement: function() {
|
||||
var rendered = this.render();
|
||||
if (rendered) {
|
||||
var elem = $(rendered);
|
||||
this.$element.replaceWith(elem);
|
||||
this.$element = elem;
|
||||
this.replaceElement($(rendered));
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
@ -394,13 +393,13 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
|||
timer = setTimeout(waitLoop, CHECK_INTERVAL);
|
||||
},
|
||||
synchronized_mode: function(to_execute) {
|
||||
var synch = this.synch;
|
||||
this.synch = true;
|
||||
try {
|
||||
return to_execute();
|
||||
} finally {
|
||||
this.synch = synch;
|
||||
}
|
||||
var synch = this.synch;
|
||||
this.synch = true;
|
||||
try {
|
||||
return to_execute();
|
||||
} finally {
|
||||
this.synch = synch;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -416,12 +415,12 @@ instance.web.Bus = instance.web.Class.extend(instance.web.EventDispatcherMixin,
|
|||
// check gtk bindings
|
||||
// http://unixpapa.com/js/key.html
|
||||
_.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) {
|
||||
$('html').on(evtype, self, function(ev) {
|
||||
$('html').on(evtype, function(ev) {
|
||||
self.trigger(evtype, ev);
|
||||
});
|
||||
});
|
||||
_.each('resize,scroll'.split(','), function(evtype) {
|
||||
$(window).on(evtype, self, function(ev) {
|
||||
$(window).on(evtype, function(ev) {
|
||||
self.trigger(evtype, ev);
|
||||
});
|
||||
});
|
||||
|
@ -541,10 +540,10 @@ $.async_when = function() {
|
|||
// special tweak for the web client
|
||||
var old_async_when = $.async_when;
|
||||
$.async_when = function() {
|
||||
if (instance.connection.synch)
|
||||
return $.when.apply(this, arguments);
|
||||
else
|
||||
return old_async_when.apply(this, arguments);
|
||||
if (instance.connection.synch)
|
||||
return $.when.apply(this, arguments);
|
||||
else
|
||||
return old_async_when.apply(this, arguments);
|
||||
};
|
||||
|
||||
/** Setup blockui */
|
||||
|
|
|
@ -803,7 +803,7 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da
|
|||
return this.ids.length;
|
||||
},
|
||||
alter_ids: function(n_ids) {
|
||||
this.ids = n_ids;
|
||||
this.ids = n_ids;
|
||||
},
|
||||
});
|
||||
instance.web.DataSetStatic = instance.web.DataSet.extend({
|
||||
|
@ -1042,7 +1042,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
|
|||
self.cache.push({id: id, values: record});
|
||||
} else {
|
||||
// I assume cache value is prioritary
|
||||
cached.values = _.defaults(_.clone(cached.values), record);
|
||||
cached.values = _.defaults(_.clone(cached.values), record);
|
||||
}
|
||||
});
|
||||
return_records();
|
||||
|
@ -1067,7 +1067,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
|
|||
return this._super(method, args, callback, error_callback);
|
||||
},
|
||||
alter_ids: function(n_ids) {
|
||||
this._super(n_ids);
|
||||
this._super(n_ids);
|
||||
this.on_change();
|
||||
},
|
||||
});
|
||||
|
|
|
@ -156,7 +156,7 @@ instance.web.DataImport = instance.web.Dialog.extend({
|
|||
});
|
||||
},
|
||||
toggle_import_button: function (newstate) {
|
||||
instance.web.dialog(this.$element, 'widget')
|
||||
instance.web.dialog(this.$element, 'widget')
|
||||
.find('.oe_import_dialog_button')
|
||||
.button('option', 'disabled', !newstate);
|
||||
},
|
||||
|
|
|
@ -661,7 +661,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
|||
null, _(this.select_for_drawer()).invoke(
|
||||
'appendTo', this.$element.find('.oe_searchview_drawer')));
|
||||
|
||||
new instance.web.search.AddToDashboard(this).appendTo($('.oe_searchview_drawer', this.$element));
|
||||
new instance.web.search.AddToReporting(this).appendTo($('.oe_searchview_drawer', this.$element));
|
||||
|
||||
// load defaults
|
||||
var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
|
||||
|
@ -861,13 +861,13 @@ instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web
|
|||
);
|
||||
}
|
||||
});
|
||||
instance.web.search.Widget = instance.web.OldWidget.extend( /** @lends instance.web.search.Widget# */{
|
||||
instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web.search.Widget# */{
|
||||
template: null,
|
||||
/**
|
||||
* Root class of all search widgets
|
||||
*
|
||||
* @constructs instance.web.search.Widget
|
||||
* @extends instance.web.OldWidget
|
||||
* @extends instance.web.Widget
|
||||
*
|
||||
* @param view the ancestor view of this widget
|
||||
*/
|
||||
|
@ -1678,42 +1678,39 @@ instance.web.search.Filters = instance.web.search.Input.extend({
|
|||
}));
|
||||
}
|
||||
});
|
||||
instance.web.search.AddToDashboard = instance.web.Widget.extend({
|
||||
template: 'SearchView.addtodashboard',
|
||||
instance.web.search.AddToReporting = instance.web.Widget.extend({
|
||||
template: 'SearchView.addtoreporting',
|
||||
_in_drawer: true,
|
||||
start: function () {
|
||||
var self = this;
|
||||
this.data_loaded = $.Deferred();
|
||||
this.dashboard_data =[];
|
||||
this.$element
|
||||
.on('click', 'h4', this.proxy('show_option'))
|
||||
.on('submit', 'form', function (e) {
|
||||
e.preventDefault();
|
||||
self.add_dashboard();
|
||||
});
|
||||
return $.when(this.load_data(),this.data_loaded).pipe(this.proxy("render_data"));
|
||||
return this.load_data().then(this.proxy("render_data"));
|
||||
},
|
||||
load_data:function(){
|
||||
if (!instance.webclient) { return $.Deferred().reject(); }
|
||||
var self = this,dashboard_menu = instance.webclient.menu.data.data.children;
|
||||
var ir_model_data = new instance.web.Model('ir.model.data',{},[['name','=','menu_reporting_dashboard']]).query(['res_id']);
|
||||
var map_data = function(result){
|
||||
_.detect(dashboard_menu, function(dash){
|
||||
var id = _.pluck(dash.children, "id"),indexof = _.indexOf(id, result.res_id);
|
||||
if(indexof !== -1){
|
||||
self.dashboard_data = dash.children[indexof].children
|
||||
self.data_loaded.resolve();
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
return ir_model_data._execute().done(function(result){map_data(result[0])});
|
||||
var dashboard_menu = instance.webclient.menu.data.data.children;
|
||||
return new instance.web.Model('ir.model.data')
|
||||
.query(['res_id'])
|
||||
.filter([['name','=','menu_reporting_dashboard']])
|
||||
.first().pipe(function (result) {
|
||||
var menu = _(dashboard_menu).chain()
|
||||
.pluck('children')
|
||||
.flatten(true)
|
||||
.find(function (child) { return child.id === result.res_id; })
|
||||
.value();
|
||||
return menu ? menu.children : [];
|
||||
});
|
||||
},
|
||||
|
||||
render_data: function(){
|
||||
var self = this;
|
||||
var selection = instance.web.qweb.render("SearchView.addtodashboard.selection",{selections:this.dashboard_data});
|
||||
this.$element.find("input").before(selection)
|
||||
render_data: function(dashboard_choices){
|
||||
var selection = instance.web.qweb.render(
|
||||
"SearchView.addtoreporting.selection", {
|
||||
selections: dashboard_choices});
|
||||
this.$("input").before(selection)
|
||||
},
|
||||
add_dashboard:function(){
|
||||
var self = this;
|
||||
|
@ -1721,11 +1718,11 @@ instance.web.search.AddToDashboard = instance.web.Widget.extend({
|
|||
var view_parent = this.getParent().getParent();
|
||||
if (! view_parent.action || ! this.$element.find("select").val())
|
||||
return this.do_warn("Can't find dashboard action");
|
||||
data = getParent.build_search_data(),
|
||||
context = new instance.web.CompoundContext(getParent.dataset.get_context() || []),
|
||||
domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []);
|
||||
_.each(data.contexts, function(x) {context.add(x);});
|
||||
_.each(data.domains, function(x) {domain.add(x);});
|
||||
var data = getParent.build_search_data();
|
||||
var context = new instance.web.CompoundContext(getParent.dataset.get_context() || []);
|
||||
var domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []);
|
||||
_.each(data.contexts, context.add, context);
|
||||
_.each(data.domains, domain.add, domain);
|
||||
this.rpc('/web/searchview/add_to_dashboard', {
|
||||
menu_id: this.$element.find("select").val(),
|
||||
action_id: view_parent.action.id,
|
||||
|
@ -1746,7 +1743,7 @@ instance.web.search.AddToDashboard = instance.web.Widget.extend({
|
|||
this.$element.toggleClass('oe_opened');
|
||||
if (! this.$element.hasClass('oe_opened'))
|
||||
return;
|
||||
this.$element.find("input").val(this.getParent().fields_view.name || "" );
|
||||
this.$("input").val(this.getParent().fields_view.name || "" );
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
_.defaults(this.options, {
|
||||
"not_interactible_on_create": false,
|
||||
"initial_mode": "view",
|
||||
"disable_autofocus": false,
|
||||
});
|
||||
this.is_initialized = $.Deferred();
|
||||
this.mutating_mutex = new $.Mutex();
|
||||
|
@ -101,7 +102,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
w.off('focused blurred');
|
||||
w.destroy();
|
||||
});
|
||||
this.$element.off('.formBlur');
|
||||
if (this.$element) {
|
||||
this.$element.off('.formBlur');
|
||||
}
|
||||
this._super();
|
||||
},
|
||||
on_loaded: function(data) {
|
||||
|
@ -267,6 +270,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
if (this.$pager) {
|
||||
this.$pager.show();
|
||||
}
|
||||
this.$element.show().css({
|
||||
opacity: '0',
|
||||
filter: 'alpha(opacity = 0)'
|
||||
});
|
||||
this.$element.add(this.$buttons).removeClass('oe_form_dirty');
|
||||
|
||||
var shown = this.has_been_loaded;
|
||||
|
@ -287,6 +294,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
if (options.editable) {
|
||||
self.to_edit_mode();
|
||||
}
|
||||
self.$element.css({
|
||||
opacity: '1',
|
||||
filter: 'alpha(opacity = 100)'
|
||||
});
|
||||
});
|
||||
},
|
||||
do_hide: function () {
|
||||
|
@ -346,6 +357,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
self.do_push_state({id:record.id});
|
||||
}
|
||||
self.$element.add(self.$buttons).removeClass('oe_form_dirty');
|
||||
self.autofocus();
|
||||
});
|
||||
},
|
||||
/**
|
||||
|
@ -646,15 +658,21 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
|||
_.each(this.fields,function(field){
|
||||
field.set({"force_readonly": false});
|
||||
});
|
||||
var fields_order = self.fields_order.slice(0);
|
||||
if (self.default_focus_field) {
|
||||
fields_order.unshift(self.default_focus_field.name);
|
||||
this.autofocus();
|
||||
}
|
||||
},
|
||||
autofocus: function() {
|
||||
if (this.get("actual_mode") !== "view" && !this.options.disable_autofocus) {
|
||||
var fields_order = this.fields_order.slice(0);
|
||||
if (this.default_focus_field) {
|
||||
fields_order.unshift(this.default_focus_field.name);
|
||||
}
|
||||
for (var i = 0; i < fields_order.length; i += 1) {
|
||||
var field = self.fields[fields_order[i]];
|
||||
var field = this.fields[fields_order[i]];
|
||||
if (!field.get('effective_invisible') && !field.get('effective_readonly')) {
|
||||
field.focus();
|
||||
break;
|
||||
if (field.focus() !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1961,14 +1979,7 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
|
|||
}
|
||||
},
|
||||
focus: function() {
|
||||
},
|
||||
/**
|
||||
* Utility method to focus an element, but only after a small amount of time.
|
||||
*/
|
||||
delay_focus: function($elem) {
|
||||
setTimeout(function() {
|
||||
$elem[0].focus();
|
||||
}, 50);
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Utility method to get the widget options defined in the field xml description.
|
||||
|
@ -2067,7 +2078,7 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we
|
|||
return this.get('value') === '' || this._super();
|
||||
},
|
||||
focus: function() {
|
||||
this.$element.find('input:first')[0].focus();
|
||||
this.$element.find('input:first').focus();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2287,8 +2298,9 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instanc
|
|||
return this.get('value') === '' || this._super();
|
||||
},
|
||||
focus: function() {
|
||||
if (this.datewidget && this.datewidget.$input)
|
||||
this.delay_focus(this.datewidget.$input);
|
||||
if (this.datewidget && this.datewidget.$input) {
|
||||
this.datewidget.$input.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2343,7 +2355,7 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we
|
|||
return this.get('value') === '' || this._super();
|
||||
},
|
||||
focus: function($element) {
|
||||
this.delay_focus(this.$textarea);
|
||||
this.$textarea.focus();
|
||||
},
|
||||
do_resize: function(max_height) {
|
||||
max_height = parseInt(max_height, 10);
|
||||
|
@ -2436,7 +2448,7 @@ instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
|
|||
this.$checkbox[0].checked = value_;
|
||||
},
|
||||
focus: function() {
|
||||
this.delay_focus(this.$checkbox);
|
||||
this.$checkbox.focus();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2460,9 +2472,6 @@ instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
|
|||
}
|
||||
});
|
||||
|
||||
instance.web.form.FieldTextXml = instance.web.form.AbstractField.extend({
|
||||
// to replace view editor
|
||||
});
|
||||
|
||||
instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
|
||||
template: 'FieldSelection',
|
||||
|
@ -2531,7 +2540,7 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instan
|
|||
return !! value_;
|
||||
},
|
||||
focus: function() {
|
||||
this.delay_focus(this.$element.find('select:first'));
|
||||
this.$element.find('select:first').focus();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2818,6 +2827,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
|||
} else if (item.action) {
|
||||
self.floating = true;
|
||||
item.action();
|
||||
// Cancel widget blurring, to avoid form blur event
|
||||
self.trigger('focused');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
@ -2921,7 +2932,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
|||
return ! this.get("value");
|
||||
},
|
||||
focus: function () {
|
||||
this.delay_focus(this.$input);
|
||||
this.$input.focus();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3077,7 +3088,6 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
|
|||
this.views = views;
|
||||
|
||||
this.viewmanager = new instance.web.form.One2ManyViewManager(this, this.dataset, views, {});
|
||||
this.viewmanager.$element.addClass("oe_view_manager_one2many");
|
||||
this.viewmanager.o2m = self;
|
||||
var once = $.Deferred().then(function() {
|
||||
self.init_form_last_update.resolve();
|
||||
|
@ -3491,10 +3501,23 @@ instance.web.form.One2ManyList = instance.web.ListView.List.extend({
|
|||
colspan: columns,
|
||||
'class': 'oe_form_field_one2many_list_row_add'
|
||||
}).text(_t("Add a row"))
|
||||
.mousedown(function () {
|
||||
// FIXME: needs to be an official API somehow
|
||||
if (self.view.editor.is_editing()) {
|
||||
self.view.__ignore_blur = true;
|
||||
}
|
||||
})
|
||||
.click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
self.view.do_add_record();
|
||||
// FIXME: there should also be an API for that one
|
||||
if (self.view.editor.form.__blur_timeout) {
|
||||
clearTimeout(self.view.editor.form.__blur_timeout);
|
||||
self.view.editor.form.__blur_timeout = false;
|
||||
}
|
||||
self.view.ensure_saved().then(function () {
|
||||
self.view.do_add_record();
|
||||
});
|
||||
});
|
||||
this.$current.append(
|
||||
$('<tr>').append($cell))
|
||||
|
@ -4305,7 +4328,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
|
|||
this.selection.view = this.view;
|
||||
this.selection.set({force_readonly: this.get('effective_readonly')});
|
||||
this.selection.on("change:value", this, this.on_selection_changed);
|
||||
this.selection.$element = $(".oe_form_view_reference_selection", this.$element);
|
||||
this.selection.setElement(this.$(".oe_form_view_reference_selection"));
|
||||
this.selection.renderElement();
|
||||
this.selection.start();
|
||||
this.selection
|
||||
|
@ -4318,7 +4341,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
|
|||
this.m2o.view = this.view;
|
||||
this.m2o.set({force_readonly: this.get("effective_readonly")});
|
||||
this.m2o.on("change:value", this, this.data_changed);
|
||||
this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element);
|
||||
this.m2o.setElement(this.$(".oe_form_view_reference_m2o"));
|
||||
this.m2o.renderElement();
|
||||
this.m2o.start();
|
||||
this.m2o
|
||||
|
@ -4698,9 +4721,6 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
|
|||
elem.css("color", color);
|
||||
}
|
||||
},
|
||||
focus: function() {
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -96,9 +96,6 @@ openerp.web.list_editable = function (instance) {
|
|||
},
|
||||
on_loaded: function (data, grouped) {
|
||||
var self = this;
|
||||
if (this.editor) {
|
||||
this.editor.destroy();
|
||||
}
|
||||
// tree/@editable takes priority on everything else if present.
|
||||
var result = this._super(data, grouped);
|
||||
if (this.editable()) {
|
||||
|
@ -118,6 +115,7 @@ openerp.web.list_editable = function (instance) {
|
|||
self.start_edition();
|
||||
}
|
||||
});
|
||||
this.editor.destroy();
|
||||
// Editor is not restartable due to formview not being
|
||||
// restartable
|
||||
this.editor = this.make_editor();
|
||||
|
@ -604,6 +602,7 @@ openerp.web.list_editable = function (instance) {
|
|||
this.form = new (this.options.formView)(
|
||||
this, this.delegate.dataset, false, {
|
||||
initial_mode: 'edit',
|
||||
disable_autofocus: true,
|
||||
$buttons: $(),
|
||||
$pager: $()
|
||||
});
|
||||
|
@ -683,9 +682,8 @@ openerp.web.list_editable = function (instance) {
|
|||
if (!field.$element.is(':visible')) {
|
||||
return false;
|
||||
}
|
||||
field.focus();
|
||||
// Stop as soon as a field got focused
|
||||
return true;
|
||||
return field.focus() !== false;
|
||||
});
|
||||
},
|
||||
edit: function (record, configureField, options) {
|
||||
|
|
|
@ -423,7 +423,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
|
||||
.parent().addClass('active');
|
||||
|
||||
$.when(view_promise).then(function () {
|
||||
return $.when(view_promise).then(function () {
|
||||
_.each(_.keys(self.views), function(view_name) {
|
||||
var controller = self.views[view_name].controller;
|
||||
if (controller) {
|
||||
|
@ -435,7 +435,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
container.hide();
|
||||
controller.do_hide();
|
||||
}
|
||||
// put the <footer> in the dialog's buttonpane
|
||||
// put the <footer> in the dialog's buttonpane
|
||||
if (self.$element.parent('.ui-dialog-content') && self.$element.find('footer')) {
|
||||
self.$element.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonset').hide()
|
||||
self.$element.find('footer').appendTo(
|
||||
|
@ -445,7 +445,6 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
}
|
||||
});
|
||||
});
|
||||
return view_promise;
|
||||
},
|
||||
do_create_view: function(view_type) {
|
||||
// Lazy loading of views
|
||||
|
@ -529,7 +528,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
|||
}
|
||||
return controller.get('title');
|
||||
});
|
||||
if (next && next.action.res_id && self.active_view === 'form' && self.model === next.action.res_model && id === next.action.res_id) {
|
||||
if (next && next.action && next.action.res_id && self.active_view === 'form' && self.model === next.action.res_model && id === next.action.res_id) {
|
||||
// If the current active view is a formview and the next item in the breadcrumbs
|
||||
// is an action on same object (model / res_id), then we omit the current formview's title
|
||||
titles.pop();
|
||||
|
@ -756,15 +755,6 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
|
|||
width: '95%'}, $root).open();
|
||||
});
|
||||
break;
|
||||
case 'manage_views':
|
||||
if (current_view.fields_view && current_view.fields_view.arch) {
|
||||
var view_editor = new instance.web.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch);
|
||||
view_editor.start();
|
||||
} else {
|
||||
this.do_warn(_t("Manage Views"),
|
||||
_t("Could not find current view declaration"));
|
||||
}
|
||||
break;
|
||||
case 'edit_workflow':
|
||||
return this.do_action({
|
||||
res_model : 'workflow',
|
||||
|
@ -1175,7 +1165,7 @@ instance.web.View = instance.web.Widget.extend({
|
|||
this.view_id = view_id;
|
||||
this.set_default_options(options);
|
||||
},
|
||||
start: function() {
|
||||
start: function () {
|
||||
return this.load_view();
|
||||
},
|
||||
load_view: function() {
|
||||
|
@ -1385,7 +1375,6 @@ instance.web.xml_to_json = function(node) {
|
|||
}
|
||||
instance.web.json_node_to_xml = function(node, human_readable, indent) {
|
||||
// For debugging purpose, this function will convert a json node back to xml
|
||||
// Maybe useful for xml view editor
|
||||
indent = indent || 0;
|
||||
var sindent = (human_readable ? (new Array(indent + 1).join('\t')) : ''),
|
||||
r = sindent + '<' + node.tag,
|
||||
|
|
|
@ -406,10 +406,10 @@
|
|||
<t t-name="ViewManager">
|
||||
<div class="oe_view_manager">
|
||||
<table class="oe_view_manager_header">
|
||||
<col width="20%"/>
|
||||
<col width="25%"/>
|
||||
<col width="20%"/>
|
||||
<col width="35%"/>
|
||||
<col width="20%"/>
|
||||
<col width="25%"/>
|
||||
<col width="20%"/>
|
||||
<col width="35%"/>
|
||||
<tr class="oe_header_row oe_header_row_top">
|
||||
<td colspan="2">
|
||||
<h2 class="oe_view_title" t-if="widget.flags.display_title !== false">
|
||||
|
@ -1443,18 +1443,18 @@
|
|||
<div>
|
||||
</div>
|
||||
</div>
|
||||
<div t-name="SearchView.addtodashboard" class="oe_searchview_dashboard">
|
||||
<h4>Add to Dashboard</h4>
|
||||
<div t-name="SearchView.addtoreporting" class="oe_searchview_dashboard">
|
||||
<h4>Add to Reporting</h4>
|
||||
<form>
|
||||
<p><input placeholder ="Title of new Dashboard item" title = "Title of new Dashboard item" type="text"/></p>
|
||||
<button class="oe_apply" type="submit">Save</button>
|
||||
<p><input placeholder="Title of new dashboard item"/></p>
|
||||
<button class="oe_apply" type="submit">Add</button>
|
||||
</form>
|
||||
</div>
|
||||
<t t-name="SearchView.addtodashboard.selection">
|
||||
<select title = "Select Dashboard to add this filter to">
|
||||
<t t-foreach="selections" t-as="element">
|
||||
<option t-att-value="element.id || element.res_id "><t t-esc="element.name"/></option>
|
||||
</t>
|
||||
<t t-name="SearchView.addtoreporting.selection">
|
||||
<select>
|
||||
<option t-foreach="selections" t-as="element"
|
||||
t-att-value="element.id || element.res_id ">
|
||||
<t t-esc="element.name"/></option>
|
||||
</select>
|
||||
</t>
|
||||
<div t-name="SearchView.advanced" class="oe_searchview_advanced">
|
||||
|
@ -1502,70 +1502,6 @@
|
|||
</select>
|
||||
</t>
|
||||
|
||||
<t t-name="view_editor">
|
||||
<table class="oe_view_editor">
|
||||
<t t-call="view_editor.row"/>
|
||||
</table>
|
||||
</t>
|
||||
<t t-name="view_editor.row">
|
||||
<tr t-att-id="'viewedit-' + rec.id" t-att-level="rec.level" t-foreach="data" t-as="rec">
|
||||
<td width="90%">
|
||||
<table class="oe_view_editor_field">
|
||||
<tr>
|
||||
<td width="16px" t-att-style="'background-position: ' + 20*rec.level + 'px; padding-left: ' + 20*rec.level + 'px'">
|
||||
<img t-if="rec.child_id.length" t-att-id="'parentimg-' + rec.id"
|
||||
t-att-src='_s + "/web/static/src/img/collapse.gif"' width="16" height="16" border="0"/>
|
||||
</td>
|
||||
<td style="cursor: pointer;">
|
||||
<a style="text-decoration:none" href="javascript:void(0);">
|
||||
<t t-esc="rec.name"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img t-if="rec.att_list.length"
|
||||
id="side-add" t-att-src='_s + "/web/static/src/img/icons/gtk-add.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img id="side-remove" t-att-src='_s + "/web/static/src/img/icons/gtk-remove.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img t-if="rec.att_list.length and !_.include(no_properties, rec.att_list[0])"
|
||||
id="side-edit" t-att-src='_s + "/web/static/src/img/icons/gtk-edit.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img t-if="rec.att_list.length"
|
||||
id="side-up" t-att-src='_s + "/web/static/src/img/icons/gtk-go-up.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img t-if="rec.att_list.length"
|
||||
id="side-down" t-att-src='_s + "/web/static/src/img/icons/gtk-go-down.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<t t-if="rec.child_id.length">
|
||||
<t t-set="data" t-value="rec.child_id"/>
|
||||
<t t-call="view_editor.row"/>
|
||||
</t>
|
||||
</tr>
|
||||
</t>
|
||||
<t t-name="vieweditor_char">
|
||||
<input type="text" t-att-id="widget.name" class="field_char" size="50"/>
|
||||
</t>
|
||||
<t t-name="vieweditor_selection">
|
||||
<select t-att-id="widget.name" >
|
||||
<t t-if="widget.selection" t-foreach="widget.selection" t-as="option">
|
||||
<option
|
||||
t-att-value="typeof option === 'object' ? option[0] : option">
|
||||
<t t-esc="typeof option === 'object' ? option[1] : option"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
<t t-name="vieweditor_boolean">
|
||||
<input type="checkbox" t-att-id="widget.name"/>
|
||||
</t>
|
||||
|
||||
<t t-name="ExportView">
|
||||
<a id="exportview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Export</a>
|
||||
</t>
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
$(document).ready(function () {
|
||||
var $fix = $('#qunit-fixture');
|
||||
var mod = {
|
||||
setup: function () {
|
||||
instance = window.openerp.init([]);
|
||||
window.openerp.web.corelib(instance);
|
||||
|
||||
instance.web.qweb = new QWeb2.Engine();
|
||||
instance.web.qweb.add_template(
|
||||
'<no>' +
|
||||
'<t t-name="test.widget.template">' +
|
||||
'<ol>' +
|
||||
'<li t-foreach="5" t-as="counter" ' +
|
||||
't-attf-class="class-#{counter}">' +
|
||||
'<input/>' +
|
||||
'<t t-esc="counter"/>' +
|
||||
'</li>' +
|
||||
'</ol>' +
|
||||
'</t>' +
|
||||
'<t t-name="test.widget.template-value">' +
|
||||
'<p><t t-esc="widget.value"/></p>' +
|
||||
'</t>' +
|
||||
'</no>');
|
||||
}
|
||||
};
|
||||
var instance;
|
||||
|
||||
module('Widget.proxy', mod);
|
||||
test('(String)', function () {
|
||||
var W = instance.web.Widget.extend({
|
||||
exec: function () {
|
||||
this.executed = true;
|
||||
}
|
||||
});
|
||||
var w = new W;
|
||||
var fn = w.proxy('exec');
|
||||
fn();
|
||||
ok(w.executed, 'should execute the named method in the right context');
|
||||
});
|
||||
test('(String)(*args)', function () {
|
||||
var W = instance.web.Widget.extend({
|
||||
exec: function (arg) {
|
||||
this.executed = arg;
|
||||
}
|
||||
});
|
||||
var w = new W;
|
||||
var fn = w.proxy('exec');
|
||||
fn(42);
|
||||
ok(w.executed, "should execute the named method in the right context");
|
||||
equal(w.executed, 42, "should be passed the proxy's arguments");
|
||||
});
|
||||
test('(String), include', function () {
|
||||
// the proxy function should handle methods being changed on the class
|
||||
// and should always proxy "by name", to the most recent one
|
||||
var W = instance.web.Widget.extend({
|
||||
exec: function () {
|
||||
this.executed = 1;
|
||||
}
|
||||
});
|
||||
var w = new W;
|
||||
var fn = w.proxy('exec');
|
||||
W.include({
|
||||
exec: function () { this.executed = 2; }
|
||||
});
|
||||
|
||||
fn();
|
||||
equal(w.executed, 2, "should be lazily resolved");
|
||||
});
|
||||
|
||||
test('(Function)', function () {
|
||||
var w = new (instance.web.Widget.extend({ }));
|
||||
|
||||
var fn = w.proxy(function () { this.executed = true; });
|
||||
fn();
|
||||
ok(w.executed, "should set the function's context (like Function#bind)");
|
||||
});
|
||||
test('(Function)(*args)', function () {
|
||||
var w = new (instance.web.Widget.extend({ }));
|
||||
|
||||
var fn = w.proxy(function (arg) { this.executed = arg; });
|
||||
fn(42);
|
||||
equal(w.executed, 42, "should be passed the proxy's arguments");
|
||||
});
|
||||
|
||||
module('Widget.renderElement', mod);
|
||||
test('no template, default', function () {
|
||||
var w = new (instance.web.Widget.extend({ }));
|
||||
|
||||
var $original = w.$element;
|
||||
ok($original, "should initially have a root element");
|
||||
w.renderElement();
|
||||
ok(w.$element, "should have generated a root element");
|
||||
ok($original !== w.$element, "should have generated a new root element");
|
||||
strictEqual(w.$element, w.$element, "should provide $element alias");
|
||||
ok(w.$element.is(w.el), "should provide raw DOM alias");
|
||||
|
||||
equal(w.el.nodeName, 'DIV', "should have generated the default element");
|
||||
equal(w.el.attributes.length, 0, "should not have generated any attribute");
|
||||
ok(_.isEmpty(w.$element.html(), "should not have generated any content"));
|
||||
});
|
||||
test('no template, custom tag', function () {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
tagName: 'ul'
|
||||
}));
|
||||
w.renderElement();
|
||||
|
||||
equal(w.el.nodeName, 'UL', "should have generated the custom element tag");
|
||||
});
|
||||
test('no template, @id', function () {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
id: 'foo'
|
||||
}));
|
||||
w.renderElement();
|
||||
|
||||
equal(w.el.attributes.length, 1, "should have one attribute");
|
||||
equal(w.$element.attr('id'), 'foo', "should have generated the id attribute");
|
||||
equal(w.el.id, 'foo', "should also be available via property");
|
||||
});
|
||||
test('no template, @className', function () {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
className: 'oe_some_class'
|
||||
}));
|
||||
w.renderElement();
|
||||
|
||||
equal(w.el.className, 'oe_some_class', "should have the right property");
|
||||
equal(w.$element.attr('class'), 'oe_some_class', "should have the right attribute");
|
||||
});
|
||||
test('no template, bunch of attributes', function () {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
attributes: {
|
||||
'id': 'some_id',
|
||||
'class': 'some_class',
|
||||
'data-foo': 'data attribute',
|
||||
'clark': 'gable',
|
||||
'spoiler': 'snape kills dumbledore'
|
||||
}
|
||||
}));
|
||||
w.renderElement();
|
||||
|
||||
equal(w.el.attributes.length, 5, "should have all the specified attributes");
|
||||
|
||||
equal(w.el.id, 'some_id');
|
||||
equal(w.$element.attr('id'), 'some_id');
|
||||
|
||||
equal(w.el.className, 'some_class');
|
||||
equal(w.$element.attr('class'), 'some_class');
|
||||
|
||||
equal(w.$element.attr('data-foo'), 'data attribute');
|
||||
equal(w.$element.data('foo'), 'data attribute');
|
||||
|
||||
equal(w.$element.attr('clark'), 'gable');
|
||||
equal(w.$element.attr('spoiler'), 'snape kills dumbledore');
|
||||
});
|
||||
|
||||
test('template', function () {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template'
|
||||
}));
|
||||
w.renderElement();
|
||||
|
||||
equal(w.el.nodeName, 'OL');
|
||||
equal(w.$element.children().length, 5);
|
||||
equal(w.el.textContent, '01234');
|
||||
});
|
||||
|
||||
module('Widget.$', mod);
|
||||
test('basic-alias', function () {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template'
|
||||
}));
|
||||
w.renderElement();
|
||||
|
||||
ok(w.$('li:eq(3)').is(w.$element.find('li:eq(3)')),
|
||||
"should do the same thing as calling find on the widget root");
|
||||
});
|
||||
|
||||
module('Widget.events', mod);
|
||||
test('delegate', function () {
|
||||
var a = [];
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template',
|
||||
events: {
|
||||
'click': function () {
|
||||
a[0] = true;
|
||||
strictEqual(this, w, "should trigger events in widget")
|
||||
},
|
||||
'click li.class-3': 'class3',
|
||||
'change input': function () { a[2] = true; }
|
||||
},
|
||||
class3: function () { a[1] = true; }
|
||||
}));
|
||||
w.renderElement();
|
||||
|
||||
w.$element.click();
|
||||
w.$('li:eq(3)').click();
|
||||
w.$('input:last').val('foo').change();
|
||||
|
||||
for(var i=0; i<3; ++i) {
|
||||
ok(a[i], "should pass test " + i);
|
||||
}
|
||||
});
|
||||
test('undelegate', function () {
|
||||
var clicked = false, newclicked = false;
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template',
|
||||
events: { 'click li': function () { clicked = true; } }
|
||||
}));
|
||||
w.renderElement();
|
||||
w.$element.on('click', 'li', function () { newclicked = true });
|
||||
|
||||
w.$('li').click();
|
||||
ok(clicked, "should trigger bound events");
|
||||
ok(newclicked, "should trigger bound events");
|
||||
clicked = newclicked = false;
|
||||
|
||||
w.undelegateEvents();
|
||||
w.$('li').click();
|
||||
ok(!clicked, "undelegate should unbind events delegated");
|
||||
ok(newclicked, "undelegate should only unbind events it created");
|
||||
});
|
||||
|
||||
module('Widget.renderElement', mod);
|
||||
test('repeated', function () {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
template: 'test.widget.template-value'
|
||||
}));
|
||||
w.value = 42;
|
||||
w.appendTo($fix)
|
||||
.always(start)
|
||||
.done(function () {
|
||||
equal($fix.find('p').text(), '42', "DOM fixture should contain initial value");
|
||||
equal(w.$element.text(), '42', "should set initial value");
|
||||
w.value = 36;
|
||||
w.renderElement();
|
||||
equal($fix.find('p').text(), '36', "DOM fixture should use new value");
|
||||
equal(w.$element.text(), '36', "should set new value");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -133,4 +133,20 @@ $(document).ready(function () {
|
|||
}]);
|
||||
deepEqual(result, {type: 'out_invoice'});
|
||||
});
|
||||
module('eval.domains', {
|
||||
setup: function () {
|
||||
openerp = window.openerp.testing.instanceFor('coresetup');
|
||||
window.openerp.web.dates(openerp);
|
||||
}
|
||||
});
|
||||
test('current_date', function () {
|
||||
var current_date = openerp.web.date_to_str(new Date());
|
||||
var result = openerp.connection.test_eval_domains(
|
||||
[[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}],
|
||||
openerp.connection.test_eval_get_context());
|
||||
deepEqual(result, [
|
||||
['name', '>=', current_date],
|
||||
['name', '<=', current_date]
|
||||
]);
|
||||
})
|
||||
});
|
||||
|
|
|
@ -72,7 +72,7 @@ $(document).ready(function () {
|
|||
});
|
||||
asyncTest('base-state', 2, function () {
|
||||
var e = new instance.web.list.Editor({
|
||||
dataset: {},
|
||||
dataset: {ids: []},
|
||||
edition_view: function () {
|
||||
return makeFormView();
|
||||
}
|
||||
|
@ -240,8 +240,7 @@ $(document).ready(function () {
|
|||
};
|
||||
|
||||
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
|
||||
var l = new instance.web.ListView({}, ds);
|
||||
l.set_editable(true);
|
||||
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||
|
||||
l.appendTo($fix)
|
||||
.pipe(l.proxy('reload_content'))
|
||||
|
@ -305,8 +304,7 @@ $(document).ready(function () {
|
|||
counter: 0,
|
||||
onEvent: function (e) { this.counter++; }
|
||||
};
|
||||
var l = new instance.web.ListView({}, ds);
|
||||
l.set_editable(true);
|
||||
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||
l.on('edit:before edit:after', o, o.onEvent);
|
||||
l.appendTo($fix)
|
||||
.pipe(l.proxy('reload_content'))
|
||||
|
@ -326,8 +324,7 @@ $(document).ready(function () {
|
|||
asyncTest('edition events: cancelling', 3, function () {
|
||||
var edit_after = false;
|
||||
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
|
||||
var l = new instance.web.ListView({}, ds);
|
||||
l.set_editable(true);
|
||||
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||
l.on('edit:before', {}, function (e) {
|
||||
e.cancel = true;
|
||||
});
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
<script src="/web/static/test/testing.js"></script>
|
||||
<script type="text/javascript">
|
||||
QUnit.config.testTimeout = 500;
|
||||
QUnit.config.testTimeout = 2000;
|
||||
</script>
|
||||
</head>
|
||||
<body id="oe" class="openerp">
|
||||
|
@ -61,5 +61,6 @@
|
|||
<script type="text/javascript" src="/web/static/test/rpc.js"></script>
|
||||
<script type="text/javascript" src="/web/static/test/evals.js"></script>
|
||||
<script type="text/javascript" src="/web/static/test/search.js"></script>
|
||||
<script type="text/javascript" src="/web/static/test/Widget.js"></script>
|
||||
<script type="text/javascript" src="/web/static/test/list-editable.js"></script>
|
||||
</html>
|
||||
|
|
|
@ -492,15 +492,17 @@ instance.web_calendar.Sidebar = instance.web.Widget.extend({
|
|||
}
|
||||
});
|
||||
instance.web_calendar.SidebarFilter = instance.web.Widget.extend({
|
||||
events: {
|
||||
'change input:checkbox': 'on_filter_click'
|
||||
},
|
||||
init: function(parent, view) {
|
||||
this._super(parent);
|
||||
this.view = view;
|
||||
this.$element.delegate('input:checkbox', 'change', this.on_filter_click);
|
||||
},
|
||||
on_events_loaded: function(filters) {
|
||||
var selected_filters = this.view.selected_filters.slice(0);
|
||||
this.$element.html(QWeb.render('CalendarView.sidebar.responsible', { filters: filters }));
|
||||
this.$element.find('div.oe_calendar_responsible input').each(function() {
|
||||
this.$('div.oe_calendar_responsible input').each(function() {
|
||||
if (_.indexOf(selected_filters, $(this).val()) > -1) {
|
||||
$(this).click();
|
||||
}
|
||||
|
@ -511,7 +513,7 @@ instance.web_calendar.SidebarFilter = instance.web.Widget.extend({
|
|||
responsibles = [],
|
||||
$e = $(e.target);
|
||||
this.view.selected_filters = [];
|
||||
this.$element.find('div.oe_calendar_responsible input:checked').each(function() {
|
||||
this.$('div.oe_calendar_responsible input:checked').each(function() {
|
||||
responsibles.push($(this).val());
|
||||
self.view.selected_filters.push($(this).val());
|
||||
});
|
||||
|
|
|
@ -226,6 +226,8 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
|
|||
}
|
||||
},
|
||||
renderElement: function() {
|
||||
this._super();
|
||||
|
||||
var check = _.detect(this.node.children, function(column, column_index) {
|
||||
return _.detect(column.children,function(element){
|
||||
return element.tag === "action"? element: false;
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
.openerp .oe_kanban_view .oe_kanban_groups {
|
||||
height: inherit;
|
||||
}
|
||||
.openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_groups {
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_header:hover .oe_dropdown_kanban {
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -65,7 +68,7 @@
|
|||
.openerp .oe_kanban_view .oe_kanban_group_header.oe_kanban_no_group {
|
||||
padding: 0px;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_column.oe_kanban_grouped, .openerp .oe_kanban_view .oe_kanban_group_header {
|
||||
.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_group_header {
|
||||
background: #f0eeee;
|
||||
border-left: 1px solid #f0f8f8;
|
||||
border-right: 1px solid #b9b9b9;
|
||||
|
@ -185,7 +188,7 @@
|
|||
font-weight: bold;
|
||||
margin: 2px 4px;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_grouped .oe_kanban_record {
|
||||
.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_record {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_gravatar {
|
||||
|
@ -236,13 +239,13 @@
|
|||
clear: both;
|
||||
text-align: center;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_grouped .oe_kanban_show_more .oe_button {
|
||||
.openerp .oe_kanban_view.oe_kanban_grouped .oe_kanban_show_more .oe_button {
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_ungrouped {
|
||||
.openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_column {
|
||||
background: white;
|
||||
}
|
||||
.openerp .oe_kanban_view .oe_kanban_ungrouped .oe_kanban_record {
|
||||
.openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_column .oe_kanban_record {
|
||||
float: left;
|
||||
padding: 2px;
|
||||
box-sizing: border-box;
|
||||
|
|
|
@ -63,6 +63,8 @@
|
|||
// KanbanGroups {{{
|
||||
.oe_kanban_groups
|
||||
height: inherit
|
||||
&.oe_kanban_ungrouped .oe_kanban_groups
|
||||
width: 100%
|
||||
.oe_kanban_header
|
||||
&:hover
|
||||
.oe_dropdown_kanban
|
||||
|
@ -86,7 +88,7 @@
|
|||
.oe_kanban_group_header.oe_kanban_no_group
|
||||
padding: 0px
|
||||
|
||||
.oe_kanban_column.oe_kanban_grouped, .oe_kanban_group_header
|
||||
&.oe_kanban_grouped .oe_kanban_column, .oe_kanban_group_header
|
||||
background: #f0eeee
|
||||
border-left: 1px solid #f0f8f8
|
||||
border-right: 1px solid #b9b9b9
|
||||
|
@ -187,7 +189,7 @@
|
|||
.oe_kanban_title
|
||||
font-weight: bold
|
||||
margin: 2px 4px
|
||||
.oe_kanban_grouped .oe_kanban_record
|
||||
&.oe_kanban_grouped .oe_kanban_record
|
||||
margin-bottom: 6px
|
||||
.oe_kanban_gravatar
|
||||
display: block
|
||||
|
@ -222,9 +224,9 @@
|
|||
.oe_kanban_show_more
|
||||
clear: both
|
||||
text-align: center
|
||||
.oe_kanban_grouped .oe_kanban_show_more .oe_button
|
||||
&.oe_kanban_grouped .oe_kanban_show_more .oe_button
|
||||
width: 100%
|
||||
.oe_kanban_ungrouped
|
||||
&.oe_kanban_ungrouped .oe_kanban_column
|
||||
background: white
|
||||
.oe_kanban_record
|
||||
float: left
|
||||
|
|
|
@ -29,8 +29,6 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
|||
records : {}
|
||||
};
|
||||
this.groups = [];
|
||||
this.form_dialog = new instance.web.form.FormDialog(this, {}, this.options.action_views_ids.form, dataset).start();
|
||||
this.form_dialog.on_form_dialog_saved.add_last(this.do_reload);
|
||||
this.aggregates = {};
|
||||
this.group_operators = ['avg', 'max', 'min', 'sum', 'count'];
|
||||
this.qweb = new QWeb2.Engine();
|
||||
|
@ -173,6 +171,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
|||
},
|
||||
do_process_groups: function(groups) {
|
||||
var self = this;
|
||||
this.$element.remove('oe_kanban_ungrouped').addClass('oe_kanban_grouped');
|
||||
this.add_group_mutex.exec(function() {
|
||||
self.do_clear_groups();
|
||||
self.dataset.ids = [];
|
||||
|
@ -194,6 +193,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
|||
},
|
||||
do_process_dataset: function(dataset) {
|
||||
var self = this;
|
||||
this.$element.remove('oe_kanban_grouped').addClass('oe_kanban_ungrouped');
|
||||
this.add_group_mutex.exec(function() {
|
||||
var def = $.Deferred();
|
||||
self.do_clear_groups();
|
||||
|
@ -702,14 +702,7 @@ instance.web_kanban.KanbanRecord = instance.web.OldWidget.extend({
|
|||
return do_it();
|
||||
},
|
||||
do_action_edit: function($action) {
|
||||
var self = this;
|
||||
if ($action.attr('target') === 'dialog') {
|
||||
this.view.form_dialog.select_id(this.id).then(function() {
|
||||
self.view.form_dialog.open();
|
||||
});
|
||||
} else {
|
||||
this.view.open_record(this.id, true);
|
||||
}
|
||||
this.view.open_record(this.id, true);
|
||||
},
|
||||
do_action_object: function ($action) {
|
||||
var button_attrs = $action.data();
|
||||
|
@ -720,9 +713,7 @@ instance.web_kanban.KanbanRecord = instance.web.OldWidget.extend({
|
|||
this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).then(function(records) {
|
||||
if (records.length) {
|
||||
self.set_record(records[0]);
|
||||
var $render = $(self.render());
|
||||
self.$element.replaceWith($render);
|
||||
self.$element = $render;
|
||||
self.replaceElement($(self.render()));
|
||||
self.$element.data('widget', self);
|
||||
self.bind_events();
|
||||
self.group.compute_cards_auto_height();
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
</td>
|
||||
</t>
|
||||
<t t-name="KanbanView.group_records_container">
|
||||
<td t-attf-class="oe_kanban_column #{widget.group ? 'oe_kanban_grouped' : 'oe_kanban_ungrouped'}">
|
||||
<td class="oe_kanban_column">
|
||||
<div class="oe_kanban_group_list_header"/>
|
||||
<div class="oe_kanban_show_more">
|
||||
<button class="oe_button">Show more... (<span class="oe_kanban_remaining"></span> remaining)</button>
|
||||
|
|
|
@ -48,8 +48,7 @@ instance.web_mobile.Login = instance.web.OldWidget.extend({
|
|||
jQuery("#oe_header").children().remove();
|
||||
this.rpc("/web/database/get_list", {}, function(result) {
|
||||
self.db_list = result.db_list;
|
||||
$('#'+self.element_id).html(self.render(self));
|
||||
self.$element = $('#'+self.element_id);
|
||||
this.setElement($('#'+self.element_id).html(self.render(self)));
|
||||
if(self.session.db!=""){
|
||||
self.$element.find("#database").val(self.session.db);
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@ openerp.web_process = function (instance) {
|
|||
_t = instance.web._t;
|
||||
instance.web.ViewManager.include({
|
||||
start: function() {
|
||||
this._super();
|
||||
var _super = this._super();
|
||||
this.process_check();
|
||||
this.process_help = this.action ? this.action.help : 'Help: Not Defined';
|
||||
this.model = this.dataset.model;
|
||||
if(this.action) this.process_model = this.action.res_model;
|
||||
else this.process_model = this.model;
|
||||
return _super;
|
||||
},
|
||||
process_check: function() {
|
||||
var self = this,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "View Editor",
|
||||
"category": "Hidden",
|
||||
"description":
|
||||
"""
|
||||
OpenERP Web to edit views.
|
||||
""",
|
||||
"version": "2.0",
|
||||
"depends":['web'],
|
||||
"js": ["static/src/js/view_editor.js"],
|
||||
"css": ['static/src/css/view_editor.css'],
|
||||
"qweb": ['static/src/xml/view_editor.xml'],
|
||||
'auto_install': True,
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2012 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2012-07-30 14:37+0530\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 0.9.6\n"
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
.openerp .oe_view_editor {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-left: -12px;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
border-spacing: 0;
|
||||
}
|
||||
.openerp .oe_view_editor td {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
border: 1px solid #d8d8d8;
|
||||
cursor: pointer;
|
||||
font-size: 90%;
|
||||
}
|
||||
.openerp .oe_view_editor_field td {
|
||||
border: 0px !important;
|
||||
}
|
||||
.openerp .oe_view_editor tr:hover {
|
||||
background-color: #ecebf2;
|
||||
}
|
|
@ -1,7 +1,25 @@
|
|||
openerp.web.view_editor = function(instance) {
|
||||
openerp.web_view_editor = function(instance) {
|
||||
var _t = instance.web._t;
|
||||
var QWeb = instance.web.qweb;
|
||||
instance.web.ViewEditor = instance.web.OldWidget.extend({
|
||||
instance.web.ViewManagerAction.include({
|
||||
on_debug_changed:function(evt){
|
||||
var val = $(evt.currentTarget).find('option:selected').val(),
|
||||
current_view = this.views[this.active_view].controller;
|
||||
if(val === "manage_views"){
|
||||
if (current_view.fields_view && current_view.fields_view.arch) {
|
||||
var view_editor = new instance.web_view_editor.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch);
|
||||
view_editor.start();
|
||||
} else {
|
||||
this.do_warn(_t("Manage Views"),
|
||||
_t("Could not find current view declaration"));
|
||||
}
|
||||
evt.currentTarget.selectedIndex = 0;
|
||||
}else{
|
||||
return this._super.apply(this,arguments);
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.web_view_editor.ViewEditor = instance.web.OldWidget.extend({
|
||||
init: function(parent, element_id, dataset, view, options) {
|
||||
this._super(parent);
|
||||
this.element_id = element_id;
|
||||
|
@ -9,7 +27,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({
|
|||
this.dataset = new instance.web.DataSetSearch(this, 'ir.ui.view', null, null),
|
||||
this.model = dataset.model;
|
||||
this.xml_element_id = 0;
|
||||
this.property = instance.web.ViewEditor.property_widget;
|
||||
this.property = instance.web_view_editor.ViewEditor.property_widget;
|
||||
this.one_object = false;
|
||||
},
|
||||
start: function() {
|
||||
|
@ -350,7 +368,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({
|
|||
this.edit_xml_dialog.$element.find("tr[id=viewedit-" + row_id + "]").addClass('ui-selected');
|
||||
},
|
||||
do_parent_img_hide_show: function(img) {
|
||||
if ($(img).attr('src') == '/web/static/src/img/collapse.gif') {
|
||||
if (_.str.include($(img).attr('src'), '/web/static/src/img/collapse.gif')) {
|
||||
$(img).attr('src', '/web/static/src/img/expand.gif');
|
||||
this.on_expand(img);
|
||||
} else {
|
||||
|
@ -434,7 +452,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({
|
|||
break;
|
||||
}
|
||||
if (view_find.attr('level') < min_level) {
|
||||
min_level = parseInt(view_find.attr('level'));
|
||||
min_level = parseInt(view_find.attr('level'));
|
||||
}
|
||||
}
|
||||
var val = _.detect(obj.att_list, function(val) {return val[0] == "name";});
|
||||
|
@ -1013,7 +1031,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({
|
|||
});
|
||||
}
|
||||
});
|
||||
instance.web.ViewEditor.Field = instance.web.Class.extend({
|
||||
instance.web_view_editor.ViewEditor.Field = instance.web.Class.extend({
|
||||
init: function(view, widget) {
|
||||
this.$element = view.$element;
|
||||
this.dirty = false;
|
||||
|
@ -1049,7 +1067,7 @@ instance.web.ViewEditor.Field = instance.web.Class.extend({
|
|||
return _.str.sprintf("<td id = %s>%s</td>", this.name, QWeb.render(this.template, {widget: this}))
|
||||
}
|
||||
});
|
||||
instance.web.ViewEditor.FieldBoolean = instance.web.ViewEditor.Field.extend({
|
||||
instance.web_view_editor.ViewEditor.FieldBoolean = instance.web_view_editor.ViewEditor.Field.extend({
|
||||
template : "vieweditor_boolean",
|
||||
start: function() {
|
||||
var self = this;
|
||||
|
@ -1067,7 +1085,7 @@ instance.web.ViewEditor.FieldBoolean = instance.web.ViewEditor.Field.extend({
|
|||
return this.$element.find("input[id=" + this.name + "]").is(':checked')? "1" : null;
|
||||
}
|
||||
});
|
||||
instance.web.ViewEditor.FieldChar = instance.web.ViewEditor.Field.extend({
|
||||
instance.web_view_editor.ViewEditor.FieldChar = instance.web_view_editor.ViewEditor.Field.extend({
|
||||
template : "vieweditor_char",
|
||||
start: function () {
|
||||
var self = this;
|
||||
|
@ -1083,7 +1101,7 @@ instance.web.ViewEditor.FieldChar = instance.web.ViewEditor.Field.extend({
|
|||
return this.$element.find("input[id=" + this.name + "]").val();
|
||||
}
|
||||
});
|
||||
instance.web.ViewEditor.FieldSelect = instance.web.ViewEditor.Field.extend({
|
||||
instance.web_view_editor.ViewEditor.FieldSelect = instance.web_view_editor.ViewEditor.Field.extend({
|
||||
template : "vieweditor_selection",
|
||||
start: function () {
|
||||
var self = this;
|
||||
|
@ -1113,7 +1131,7 @@ instance.web.ViewEditor.FieldSelect = instance.web.ViewEditor.Field.extend({
|
|||
return this.$element.find("select[id=" + this.name + "]").val();
|
||||
}
|
||||
});
|
||||
instance.web.ViewEditor.FieldSelectMulti = instance.web.ViewEditor.FieldSelect.extend({
|
||||
instance.web_view_editor.ViewEditor.FieldSelectMulti = instance.web_view_editor.ViewEditor.FieldSelect.extend({
|
||||
start: function () {
|
||||
this._super();
|
||||
this.$element.find("select[id=" + this.name + "]").css('height', '100px').attr("multiple", true);
|
||||
|
@ -1129,7 +1147,7 @@ instance.web.ViewEditor.FieldSelectMulti = instance.web.ViewEditor.FieldSelect.e
|
|||
});
|
||||
}
|
||||
});
|
||||
instance.web.ViewEditor.FieldFloat = instance.web.ViewEditor.FieldChar.extend({
|
||||
instance.web_view_editor.ViewEditor.FieldFloat = instance.web_view_editor.ViewEditor.FieldChar.extend({
|
||||
});
|
||||
|
||||
var _PROPERTIES = {
|
||||
|
@ -1202,11 +1220,11 @@ var _ICONS = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
|
|||
'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
|
||||
'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph'
|
||||
];
|
||||
instance.web.ViewEditor.property_widget = new instance.web.Registry({
|
||||
'boolean' : 'instance.web.ViewEditor.FieldBoolean',
|
||||
'selection_multi' : 'instance.web.ViewEditor.FieldSelectMulti',
|
||||
'selection' : 'instance.web.ViewEditor.FieldSelect',
|
||||
'char' : 'instance.web.ViewEditor.FieldChar',
|
||||
'float' : 'instance.web.ViewEditor.FieldFloat'
|
||||
instance.web_view_editor.ViewEditor.property_widget = new instance.web.Registry({
|
||||
'boolean' : 'instance.web_view_editor.ViewEditor.FieldBoolean',
|
||||
'selection_multi' : 'instance.web_view_editor.ViewEditor.FieldSelectMulti',
|
||||
'selection' : 'instance.web_view_editor.ViewEditor.FieldSelect',
|
||||
'char' : 'instance.web_view_editor.ViewEditor.FieldChar',
|
||||
'float' : 'instance.web_view_editor.ViewEditor.FieldFloat'
|
||||
});
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<template>
|
||||
<t t-name="view_editor">
|
||||
<table class="oe_view_editor">
|
||||
<t t-call="view_editor.row"/>
|
||||
</table>
|
||||
</t>
|
||||
<t t-name="view_editor.row">
|
||||
<tr t-att-id="'viewedit-' + rec.id" t-att-level="rec.level" t-foreach="data" t-as="rec">
|
||||
<td width="90%">
|
||||
<table class="oe_view_editor_field">
|
||||
<tr>
|
||||
<td width="16px" t-att-style="'background-position: ' + 20*rec.level + 'px; padding-left: ' + 20*rec.level + 'px'">
|
||||
<img t-if="rec.child_id.length" t-att-id="'parentimg-' + rec.id"
|
||||
t-att-src='_s + "/web/static/src/img/collapse.gif"' width="16" height="16" border="0"/>
|
||||
</td>
|
||||
<td style="cursor: pointer;">
|
||||
<a style="text-decoration:none" href="javascript:void(0);">
|
||||
<t t-esc="rec.name"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img t-if="rec.att_list.length"
|
||||
id="side-add" t-att-src='_s + "/web/static/src/img/icons/gtk-add.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img id="side-remove" t-att-src='_s + "/web/static/src/img/icons/gtk-remove.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img t-if="rec.att_list.length and !_.include(no_properties, rec.att_list[0])"
|
||||
id="side-edit" t-att-src='_s + "/web/static/src/img/icons/gtk-edit.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img t-if="rec.att_list.length"
|
||||
id="side-up" t-att-src='_s + "/web/static/src/img/icons/gtk-go-up.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<td width="2%">
|
||||
<img t-if="rec.att_list.length"
|
||||
id="side-down" t-att-src='_s + "/web/static/src/img/icons/gtk-go-down.png"' style="cursor: pointer;"/>
|
||||
</td>
|
||||
<t t-if="rec.child_id.length">
|
||||
<t t-set="data" t-value="rec.child_id"/>
|
||||
<t t-call="view_editor.row"/>
|
||||
</t>
|
||||
</tr>
|
||||
</t>
|
||||
<t t-name="vieweditor_char">
|
||||
<input type="text" t-att-id="widget.name" class="field_char" size="50"/>
|
||||
</t>
|
||||
<t t-name="vieweditor_selection">
|
||||
<select t-att-id="widget.name" >
|
||||
<t t-if="widget.selection" t-foreach="widget.selection" t-as="option">
|
||||
<option
|
||||
t-att-value="typeof option === 'object' ? option[0] : option">
|
||||
<t t-esc="typeof option === 'object' ? option[1] : option"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</t>
|
||||
<t t-name="vieweditor_boolean">
|
||||
<input type="checkbox" t-att-id="widget.name"/>
|
||||
</t>
|
||||
</template>
|
109
doc/addons.rst
109
doc/addons.rst
|
@ -113,103 +113,6 @@ initializing the addon.
|
|||
Creating new standard roles
|
||||
---------------------------
|
||||
|
||||
Widget
|
||||
++++++
|
||||
|
||||
This is the base class for all visual components. It provides a number of
|
||||
services for the management of a DOM subtree:
|
||||
|
||||
* Rendering with QWeb
|
||||
|
||||
* Parenting-child relations
|
||||
|
||||
* Life-cycle management (including facilitating children destruction when a
|
||||
parent object is removed)
|
||||
|
||||
* DOM insertion, via jQuery-powered insertion methods. Insertion targets can
|
||||
be anything the corresponding jQuery method accepts (generally selectors,
|
||||
DOM nodes and jQuery objects):
|
||||
|
||||
:js:func:`~openerp.base.Widget.appendTo`
|
||||
Renders the widget and inserts it as the last child of the target, uses
|
||||
`.appendTo()`_
|
||||
|
||||
:js:func:`~openerp.base.Widget.prependTo`
|
||||
Renders the widget and inserts it as the first child of the target, uses
|
||||
`.prependTo()`_
|
||||
|
||||
:js:func:`~openerp.base.Widget.insertAfter`
|
||||
Renders the widget and inserts it as the preceding sibling of the target,
|
||||
uses `.insertAfter()`_
|
||||
|
||||
:js:func:`~openerp.base.Widget.insertBefore`
|
||||
Renders the widget and inserts it as the following sibling of the target,
|
||||
uses `.insertBefore()`_
|
||||
|
||||
:js:class:`~openerp.base.Widget` inherits from
|
||||
:js:class:`~openerp.base.SessionAware`, so subclasses can easily access the
|
||||
RPC layers.
|
||||
|
||||
Subclassing Widget
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:js:class:`~openerp.base.Widget` is subclassed in the standard manner (via the
|
||||
:js:func:`~openerp.base.Class.extend` method), and provides a number of
|
||||
abstract properties and concrete methods (which you may or may not want to
|
||||
override). Creating a subclass looks like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyWidget = openerp.base.Widget.extend({
|
||||
// QWeb template to use when rendering the object
|
||||
template: "MyQWebTemplate",
|
||||
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
// insert code to execute before rendering, for object
|
||||
// initialization
|
||||
},
|
||||
start: function() {
|
||||
this._super();
|
||||
// post-rendering initialization code, at this point
|
||||
// ``this.$element`` has been initialized
|
||||
this.$element.find(".my_button").click(/* an example of event binding * /);
|
||||
|
||||
// if ``start`` is asynchronous, return a promise object so callers
|
||||
// know when the object is done initializing
|
||||
return this.rpc(/* … */)
|
||||
}
|
||||
});
|
||||
|
||||
The new class can then be used in the following manner:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// Create the instance
|
||||
var my_widget = new MyWidget(this);
|
||||
// Render and insert into DOM
|
||||
my_widget.appendTo(".some-div");
|
||||
|
||||
After these two lines have executed (and any promise returned by ``appendTo``
|
||||
has been resolved if needed), the widget is ready to be used.
|
||||
|
||||
.. note:: the insertion methods will start the widget themselves, and will
|
||||
return the result of :js:func:`~openerp.base.Widget.start()`.
|
||||
|
||||
If for some reason you do not want to call these methods, you will
|
||||
have to first call :js:func:`~openerp.base.Widget.render()` on the
|
||||
widget, then insert it into your DOM and start it.
|
||||
|
||||
If the widget is not needed anymore (because it's transient), simply terminate
|
||||
it:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
my_widget.stop();
|
||||
|
||||
will unbind all DOM events, remove the widget's content from the DOM and
|
||||
destroy all widget data.
|
||||
|
||||
Views
|
||||
+++++
|
||||
|
||||
|
@ -541,18 +444,6 @@ Python
|
|||
.. _promise object:
|
||||
http://api.jquery.com/deferred.promise/
|
||||
|
||||
.. _.appendTo():
|
||||
http://api.jquery.com/appendTo/
|
||||
|
||||
.. _.prependTo():
|
||||
http://api.jquery.com/prependTo/
|
||||
|
||||
.. _.insertAfter():
|
||||
http://api.jquery.com/insertAfter/
|
||||
|
||||
.. _.insertBefore():
|
||||
http://api.jquery.com/insertBefore/
|
||||
|
||||
.. _Rosetta:
|
||||
.. _Launchpad's own translation tool:
|
||||
https://help.launchpad.net/Translations
|
||||
|
|
|
@ -16,6 +16,7 @@ Contents:
|
|||
async
|
||||
rpc
|
||||
|
||||
widget
|
||||
search-view
|
||||
|
||||
list-view
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
User Interaction: Widget
|
||||
========================
|
||||
|
||||
This is the base class for all visual components. It corresponds to an MVC
|
||||
view. It provides a number of services to handle a section of a page:
|
||||
|
||||
* Rendering with QWeb
|
||||
|
||||
* Parenting-child relations
|
||||
|
||||
* Life-cycle management (including facilitating children destruction when a
|
||||
parent object is removed)
|
||||
|
||||
* DOM insertion, via jQuery-powered insertion methods. Insertion targets can
|
||||
be anything the corresponding jQuery method accepts (generally selectors,
|
||||
DOM nodes and jQuery objects):
|
||||
|
||||
:js:func:`~openerp.base.Widget.appendTo`
|
||||
Renders the widget and inserts it as the last child of the target, uses
|
||||
`.appendTo()`_
|
||||
|
||||
:js:func:`~openerp.base.Widget.prependTo`
|
||||
Renders the widget and inserts it as the first child of the target, uses
|
||||
`.prependTo()`_
|
||||
|
||||
:js:func:`~openerp.base.Widget.insertAfter`
|
||||
Renders the widget and inserts it as the preceding sibling of the target,
|
||||
uses `.insertAfter()`_
|
||||
|
||||
:js:func:`~openerp.base.Widget.insertBefore`
|
||||
Renders the widget and inserts it as the following sibling of the target,
|
||||
uses `.insertBefore()`_
|
||||
|
||||
* Backbone-compatible shortcuts
|
||||
|
||||
DOM Root
|
||||
--------
|
||||
|
||||
A :js:class:`~openerp.web.Widget` is responsible for a section of the
|
||||
page materialized by the DOM root of the widget. The DOM root is
|
||||
available via the :js:attr:`~openerp.web.Widget.el` and
|
||||
:js:attr:`~openerp.web.Widget.$element` attributes, which are
|
||||
respectively the raw DOM Element and the jQuery wrapper around the DOM
|
||||
element.
|
||||
|
||||
There are two main ways to define and generate this DOM root:
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.template
|
||||
|
||||
Should be set to the name of a QWeb template (a
|
||||
:js:class:`String`). If set, the template will be rendered after
|
||||
the widget has been initialized but before it has been
|
||||
started. The root element generated by the template will be set as
|
||||
the DOM root of the widget.
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.tagName
|
||||
|
||||
Used if the widget has no template defined. Defaults to ``div``,
|
||||
will be used as the tag name to create the DOM element to set as
|
||||
the widget's DOM root. It is possible to further customize this
|
||||
generated DOM root with the following attributes:
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.id
|
||||
|
||||
Used to generate an ``id`` attribute on the generated DOM
|
||||
root.
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.className
|
||||
|
||||
Used to generate a ``class`` attribute on the generated DOM root.
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.attributes
|
||||
|
||||
Mapping (object literal) of attribute names to attribute
|
||||
values. Each of these k:v pairs will be set as a DOM attribute
|
||||
on the generated DOM root.
|
||||
|
||||
None of these is used in case a template is specified on the widget.
|
||||
|
||||
The DOM root can also be defined programmatically by overridding
|
||||
|
||||
.. js:function:: openerp.web.Widget.renderElement
|
||||
|
||||
Renders the widget's DOM root and sets it. The default
|
||||
implementation will render a set template or generate an element
|
||||
as described above, and will call
|
||||
:js:func:`~openerp.web.Widget.setElement` on the result.
|
||||
|
||||
Any override to :js:func:`~openerp.web.Widget.renderElement` which
|
||||
does not call its ``_super`` **must** call
|
||||
:js:func:`~openerp.web.Widget.setElement` with whatever it
|
||||
generated or the widget's behavior is undefined.r
|
||||
|
||||
.. note::
|
||||
|
||||
The default :js:func:`~openerp.web.Widget.renderElement` can
|
||||
be called repeatedly, it will *replace* the previous DOM root
|
||||
(using ``replaceWith``). However, this requires that the
|
||||
widget correctly sets and unsets its events (and children
|
||||
widgets). Generally,
|
||||
:js:func:`~openerp.web.Widget.renderElement` should not be
|
||||
called repeatedly unless the widget advertizes this feature.
|
||||
|
||||
Accessing DOM content
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Because a widget is only responsible for the content below its DOM
|
||||
root, there is a shortcut for selecting sub-sections of a widget's
|
||||
DOM:
|
||||
|
||||
.. js:function:: openerp.web.Widget.$(selector)
|
||||
|
||||
Applies the CSS selector specified as parameter to the widget's
|
||||
DOM root.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
this.$(selector);
|
||||
|
||||
is functionally identical to:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
this.$element.find(selector);
|
||||
|
||||
:param String selector: CSS selector
|
||||
:returns: jQuery object
|
||||
|
||||
.. note:: this helper method is compatible with
|
||||
``Backbone.View.$``
|
||||
|
||||
Resetting the DOM root
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. js:function:: openerp.web.Widget.setElement(element)
|
||||
|
||||
Re-sets the widget's DOM root to the provided element, also
|
||||
handles re-setting the various aliases of the DOM root as well as
|
||||
unsetting and re-setting delegated events.
|
||||
|
||||
:param Element element: a DOM element or jQuery object to set as
|
||||
the widget's DOM root
|
||||
|
||||
.. note:: should be mostly compatible with `Backbone's
|
||||
setElement`_
|
||||
|
||||
DOM events handling
|
||||
-------------------
|
||||
|
||||
A widget will generally need to respond to user action within its
|
||||
section of the page. This entails binding events to DOM elements.
|
||||
|
||||
To this end, :js:class:`~openerp.web.Widget` provides an shortcut:
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.events
|
||||
|
||||
Events are a mapping of ``event selector`` (an event name and a
|
||||
CSS selector separated by a space) to a callback. The callback can
|
||||
be either a method name in the widget or a function. In either
|
||||
case, the ``this`` will be set to the widget.
|
||||
|
||||
The selector is used for jQuery's `event delegation`_, the
|
||||
callback will only be triggered for descendants of the DOM root
|
||||
matching the selector [0]_. If the selector is left out (only an
|
||||
event name is specified), the event will be set directly on the
|
||||
widget's DOM root.
|
||||
|
||||
.. js:function:: openerp.web.Widget.delegateEvents
|
||||
|
||||
This method is in charge of binding
|
||||
:js:attr:`~openerp.web.Widget.events` to the DOM. It is
|
||||
automatically called after setting the widget's DOM root.
|
||||
|
||||
It can be overridden to set up more complex events than the
|
||||
:js:attr:`~openerp.web.Widget.events` map allows, but the parent
|
||||
should always be called (or :js:attr:`~openerp.web.Widget.events`
|
||||
won't be handled correctly).
|
||||
|
||||
.. js:function:: openerp.web.Widget.undelegateEvents
|
||||
|
||||
This method is in charge of unbinding
|
||||
:js:attr:`~openerp.web.Widget.events` from the DOM root when the
|
||||
widget is destroyed or the DOM root is reset, in order to avoid
|
||||
leaving "phantom" events.
|
||||
|
||||
It should be overridden to un-set any event set in an override of
|
||||
:js:func:`~openerp.web.Widget.delegateEvents`.
|
||||
|
||||
.. note:: this behavior should be compatible with `Backbone's
|
||||
delegateEvents`_, apart from not accepting any argument.
|
||||
|
||||
Subclassing Widget
|
||||
------------------
|
||||
|
||||
:js:class:`~openerp.base.Widget` is subclassed in the standard manner (via the
|
||||
:js:func:`~openerp.base.Class.extend` method), and provides a number of
|
||||
abstract properties and concrete methods (which you may or may not want to
|
||||
override). Creating a subclass looks like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyWidget = openerp.base.Widget.extend({
|
||||
// QWeb template to use when rendering the object
|
||||
template: "MyQWebTemplate",
|
||||
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
// insert code to execute before rendering, for object
|
||||
// initialization
|
||||
},
|
||||
start: function() {
|
||||
this._super();
|
||||
// post-rendering initialization code, at this point
|
||||
// ``this.$element`` has been initialized
|
||||
this.$element.find(".my_button").click(/* an example of event binding * /);
|
||||
|
||||
// if ``start`` is asynchronous, return a promise object so callers
|
||||
// know when the object is done initializing
|
||||
return this.rpc(/* … */)
|
||||
}
|
||||
});
|
||||
|
||||
The new class can then be used in the following manner:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// Create the instance
|
||||
var my_widget = new MyWidget(this);
|
||||
// Render and insert into DOM
|
||||
my_widget.appendTo(".some-div");
|
||||
|
||||
After these two lines have executed (and any promise returned by ``appendTo``
|
||||
has been resolved if needed), the widget is ready to be used.
|
||||
|
||||
.. note:: the insertion methods will start the widget themselves, and will
|
||||
return the result of :js:func:`~openerp.base.Widget.start()`.
|
||||
|
||||
If for some reason you do not want to call these methods, you will
|
||||
have to first call :js:func:`~openerp.base.Widget.render()` on the
|
||||
widget, then insert it into your DOM and start it.
|
||||
|
||||
If the widget is not needed anymore (because it's transient), simply terminate
|
||||
it:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
my_widget.destroy();
|
||||
|
||||
will unbind all DOM events, remove the widget's content from the DOM and
|
||||
destroy all widget data.
|
||||
|
||||
.. [0] not all DOM events are compatible with events delegation
|
||||
|
||||
.. _.appendTo():
|
||||
http://api.jquery.com/appendTo/
|
||||
|
||||
.. _.prependTo():
|
||||
http://api.jquery.com/prependTo/
|
||||
|
||||
.. _.insertAfter():
|
||||
http://api.jquery.com/insertAfter/
|
||||
|
||||
.. _.insertBefore():
|
||||
http://api.jquery.com/insertBefore/
|
||||
|
||||
.. _event delegation:
|
||||
http://api.jquery.com/delegate/
|
||||
|
||||
.. _Backbone's setElement:
|
||||
http://backbonejs.org/#View-setElement
|
||||
|
||||
.. _Backbone's delegateEvents:
|
||||
http://backbonejs.org/#View-delegateEvents
|
||||
|
Loading…
Reference in New Issue