[MERGE] trunk
bzr revid: al@openerp.com-20120801142851-kmc1obvwu6kdwdin
This commit is contained in:
commit
2948fca067
|
@ -3,8 +3,10 @@
|
||||||
"category": "Hidden",
|
"category": "Hidden",
|
||||||
"description":
|
"description":
|
||||||
"""
|
"""
|
||||||
OpenERP Web core module.
|
OpenERP Web core module.
|
||||||
This module provides the core of the OpenERP Web Client.
|
========================
|
||||||
|
|
||||||
|
This module provides the core of the OpenERP Web Client.
|
||||||
""",
|
""",
|
||||||
"depends" : [],
|
"depends" : [],
|
||||||
'auto_install': True,
|
'auto_install': True,
|
||||||
|
|
|
@ -425,11 +425,14 @@ class DisableCacheMiddleware(object):
|
||||||
def start_wrapped(status, headers):
|
def start_wrapped(status, headers):
|
||||||
referer = environ.get('HTTP_REFERER', '')
|
referer = environ.get('HTTP_REFERER', '')
|
||||||
parsed = urlparse.urlparse(referer)
|
parsed = urlparse.urlparse(referer)
|
||||||
debug = not urlparse.parse_qs(parsed.query).has_key('debug')
|
debug = parsed.query.count('debug') >= 1
|
||||||
filtered_headers = [(k,v) for k,v in headers if not (k=='Last-Modified' or (debug and k=='Cache-Control'))]
|
nh = dict(headers)
|
||||||
|
if 'Last-Modified' in nh: del nh['Last-Modified']
|
||||||
if debug:
|
if debug:
|
||||||
filtered_headers.append(('Cache-Control', 'no-cache'))
|
if 'Expires' in nh: del nh['Expires']
|
||||||
start_response(status, filtered_headers)
|
if 'Etag' in nh: del nh['Etag']
|
||||||
|
nh['Cache-Control'] = 'no-cache'
|
||||||
|
start_response(status, nh.items())
|
||||||
return self.app(environ, start_wrapped)
|
return self.app(environ, start_wrapped)
|
||||||
|
|
||||||
class Root(object):
|
class Root(object):
|
||||||
|
|
|
@ -194,9 +194,12 @@ class WebClient(openerpweb.Controller):
|
||||||
if mods is not None:
|
if mods is not None:
|
||||||
path += '?mods=' + mods
|
path += '?mods=' + mods
|
||||||
return [path]
|
return [path]
|
||||||
# old code to force cache reloading, this really works, sorry niv but we stop wasting time
|
no_sugar = req.httprequest.environ["QUERY_STRING"].count("no_sugar") >= 1
|
||||||
return ['%s?debug=%s' % (wp, os.path.getmtime(fp)) for fp, wp in self.manifest_glob(req, mods, extension)]
|
no_sugar = no_sugar or req.httprequest.environ.get('HTTP_REFERER', '').count("no_sugar") >= 1
|
||||||
return [el[1] for el in self.manifest_glob(req, mods, extension)]
|
if not no_sugar:
|
||||||
|
return ['%s?debug=%s' % (wp, os.path.getmtime(fp)) for fp, wp in self.manifest_glob(req, mods, extension)]
|
||||||
|
else:
|
||||||
|
return [el[1] for el in self.manifest_glob(req, mods, extension)]
|
||||||
|
|
||||||
@openerpweb.jsonrequest
|
@openerpweb.jsonrequest
|
||||||
def csslist(self, req, mods=None):
|
def csslist(self, req, mods=None):
|
||||||
|
@ -994,6 +997,16 @@ class DataSet(openerpweb.Controller):
|
||||||
elif isinstance(kwargs[k], common.nonliterals.BaseDomain):
|
elif isinstance(kwargs[k], common.nonliterals.BaseDomain):
|
||||||
kwargs[k] = req.session.eval_domain(kwargs[k])
|
kwargs[k] = req.session.eval_domain(kwargs[k])
|
||||||
|
|
||||||
|
# Temporary implements future display_name special field for model#read()
|
||||||
|
if method == 'read' and kwargs.get('context') and kwargs['context'].get('future_display_name'):
|
||||||
|
if 'display_name' in args[1]:
|
||||||
|
names = req.session.model(model).name_get(args[0], **kwargs)
|
||||||
|
args[1].remove('display_name')
|
||||||
|
r = getattr(req.session.model(model), method)(*args, **kwargs)
|
||||||
|
for i in range(len(r)):
|
||||||
|
r[i]['display_name'] = names[i][1] or "%s#%d" % (model, names[i][0])
|
||||||
|
return r
|
||||||
|
|
||||||
return getattr(req.session.model(model), method)(*args, **kwargs)
|
return getattr(req.session.model(model), method)(*args, **kwargs)
|
||||||
|
|
||||||
@openerpweb.jsonrequest
|
@openerpweb.jsonrequest
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1320,6 +1320,9 @@
|
||||||
.openerp .oe_view_manager .oe_view_manager_pager {
|
.openerp .oe_view_manager .oe_view_manager_pager {
|
||||||
line-height: 26px;
|
line-height: 26px;
|
||||||
}
|
}
|
||||||
|
.openerp .oe_view_manager .oe_view_manager_pager .oe_list_pager_single_page .oe_pager_group {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.openerp .oe_view_manager .oe_pager_value {
|
.openerp .oe_view_manager .oe_pager_value {
|
||||||
float: left;
|
float: left;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
|
|
@ -1019,6 +1019,8 @@ $sheet-max-width: 860px
|
||||||
// ViewManager.pager {{{
|
// ViewManager.pager {{{
|
||||||
.oe_view_manager_pager
|
.oe_view_manager_pager
|
||||||
line-height: 26px
|
line-height: 26px
|
||||||
|
.oe_list_pager_single_page .oe_pager_group
|
||||||
|
display: none
|
||||||
.oe_pager_value
|
.oe_pager_value
|
||||||
float: left
|
float: left
|
||||||
margin-right: 8px
|
margin-right: 8px
|
||||||
|
|
|
@ -57,9 +57,7 @@ instance.web.Dialog = instance.web.Widget.extend({
|
||||||
init: function (parent, options, content) {
|
init: function (parent, options, content) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this._super(parent);
|
this._super(parent);
|
||||||
if (content) {
|
this.setElement(content || this.make(this.tagName));
|
||||||
this.$element = content instanceof $ ? content : $(content);
|
|
||||||
}
|
|
||||||
this.dialog_options = {
|
this.dialog_options = {
|
||||||
modal: true,
|
modal: true,
|
||||||
destroy_on_close: true,
|
destroy_on_close: true,
|
||||||
|
@ -584,25 +582,21 @@ instance.web.Login = instance.web.Widget.extend({
|
||||||
localStorage.setItem('last_password_login_success', '');
|
localStorage.setItem('last_password_login_success', '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.do_action("login_sucessful");
|
self.trigger('login_successful');
|
||||||
},function () {
|
},function () {
|
||||||
self.$(".oe_login_pane").fadeIn("fast");
|
self.$(".oe_login_pane").fadeIn("fast");
|
||||||
self.$element.addClass("oe_login_invalid");
|
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");
|
instance.web.client_actions.add("login", "instance.web.Login");
|
||||||
|
|
||||||
instance.web.LoginSuccessful = instance.web.Widget.extend({
|
|
||||||
init: function(parent) {
|
|
||||||
this._super(parent);
|
|
||||||
},
|
|
||||||
start: function() {
|
|
||||||
this.getParent().getParent().show_application();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
instance.web.client_actions.add("login_sucessful", "instance.web.LoginSuccessful");
|
|
||||||
|
|
||||||
instance.web.Menu = instance.web.Widget.extend({
|
instance.web.Menu = instance.web.Widget.extend({
|
||||||
template: 'Menu',
|
template: 'Menu',
|
||||||
init: function() {
|
init: function() {
|
||||||
|
@ -890,12 +884,11 @@ instance.web.Client = instance.web.Widget.extend({
|
||||||
},
|
},
|
||||||
start: function() {
|
start: function() {
|
||||||
var self = this;
|
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, {}));
|
var $e = $(QWeb.render(self._template, {}));
|
||||||
self.$element.replaceWith($e);
|
self.replaceElement($e);
|
||||||
self.$element = $e;
|
|
||||||
self.bind_events();
|
self.bind_events();
|
||||||
self.show_common();
|
return self.show_common();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
bind_events: function() {
|
bind_events: function() {
|
||||||
|
@ -906,7 +899,8 @@ instance.web.Client = instance.web.Widget.extend({
|
||||||
this.$element.on('click', '.oe_dropdown_toggle', function(ev) {
|
this.$element.on('click', '.oe_dropdown_toggle', function(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
var $toggle = $(this);
|
var $toggle = $(this);
|
||||||
var $menu = $toggle.find('.oe_dropdown_menu');
|
var $menu = $toggle.siblings('.oe_dropdown_menu');
|
||||||
|
$menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu');
|
||||||
var state = $menu.is('.oe_opened');
|
var state = $menu.is('.oe_opened');
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
// Do not alter propagation
|
// Do not alter propagation
|
||||||
|
@ -923,8 +917,10 @@ instance.web.Client = instance.web.Widget.extend({
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
instance.web.bus.on('click', this, function() {
|
instance.web.bus.on('click', this, function(ev) {
|
||||||
self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened');
|
if (!$(ev.target).is('input[type=file]')) {
|
||||||
|
self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
show_common: function() {
|
show_common: function() {
|
||||||
|
@ -979,10 +975,9 @@ instance.web.WebClient = instance.web.Client.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
show_login: function() {
|
show_login: function() {
|
||||||
var self = this;
|
this.$('.oe_topbar').hide();
|
||||||
self.$('.oe_topbar').hide();
|
this.action_manager.do_action("login");
|
||||||
self.action_manager.do_action("login");
|
this.action_manager.inner_widget.on('login_successful', this, this.show_application);
|
||||||
//self.login.appendTo(self.$element);
|
|
||||||
},
|
},
|
||||||
show_application: function() {
|
show_application: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
|
@ -378,7 +378,8 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
|
||||||
instance.web.EventDispatcherMixin.init.call(this);
|
instance.web.EventDispatcherMixin.init.call(this);
|
||||||
this.__getterSetterInternalMap = {};
|
this.__getterSetterInternalMap = {};
|
||||||
},
|
},
|
||||||
set: function(map) {
|
set: function(map, options) {
|
||||||
|
options = options || {};
|
||||||
var self = this;
|
var self = this;
|
||||||
var changed = false;
|
var changed = false;
|
||||||
_.each(map, function(val, key) {
|
_.each(map, function(val, key) {
|
||||||
|
@ -387,10 +388,11 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
|
||||||
return;
|
return;
|
||||||
changed = true;
|
changed = true;
|
||||||
self.__getterSetterInternalMap[key] = val;
|
self.__getterSetterInternalMap[key] = val;
|
||||||
self.trigger("change:" + key, self, {
|
if (! options.silent)
|
||||||
oldValue: tmp,
|
self.trigger("change:" + key, self, {
|
||||||
newValue: val
|
oldValue: tmp,
|
||||||
});
|
newValue: val
|
||||||
|
});
|
||||||
});
|
});
|
||||||
if (changed)
|
if (changed)
|
||||||
self.trigger("change", self);
|
self.trigger("change", self);
|
||||||
|
@ -489,23 +491,19 @@ instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, {
|
||||||
*
|
*
|
||||||
* The semantics of this precisely replace closing over the method call.
|
* 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
|
* @returns {Function} proxied method
|
||||||
*/
|
*/
|
||||||
proxy: function (method_name) {
|
proxy: function (method) {
|
||||||
var self = this;
|
var self = this;
|
||||||
return function () {
|
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, {
|
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.
|
* Constructs the widget and sets its parent if a parent is given.
|
||||||
*
|
*
|
||||||
|
@ -515,14 +513,9 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
||||||
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
|
* @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
|
* When that widget is destroyed by calling destroy(), the current instance will be
|
||||||
* destroyed too. Can be null.
|
* 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) {
|
init: function(parent) {
|
||||||
instance.web.CallbackEnabledMixin.init.call(this);
|
instance.web.CallbackEnabledMixin.init.call(this);
|
||||||
this.$element = $(document.createElement(this.tagName));
|
|
||||||
this.setParent(parent);
|
this.setParent(parent);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -532,7 +525,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
||||||
_.each(this.getChildren(), function(el) {
|
_.each(this.getChildren(), function(el) {
|
||||||
el.destroy();
|
el.destroy();
|
||||||
});
|
});
|
||||||
if(this.$element != null) {
|
if(this.$element) {
|
||||||
this.$element.remove();
|
this.$element.remove();
|
||||||
}
|
}
|
||||||
instance.web.PropertiesMixin.destroy.call(this);
|
instance.web.PropertiesMixin.destroy.call(this);
|
||||||
|
@ -611,6 +604,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
||||||
* @returns {jQuery.Deferred}
|
* @returns {jQuery.Deferred}
|
||||||
*/
|
*/
|
||||||
start: function() {
|
start: function() {
|
||||||
|
return $.when();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -671,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.
|
* 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, {
|
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
|
* 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.
|
* redefined in subclasses or the default render() method can not be used.
|
||||||
|
@ -687,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.
|
* @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
|
* When that widget is destroyed by calling destroy(), the current instance will be
|
||||||
* destroyed too. Can be null.
|
* 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) {
|
init: function(parent) {
|
||||||
instance.web.WidgetMixin.init.call(this,parent);
|
instance.web.WidgetMixin.init.call(this,parent);
|
||||||
|
// FIXME: this should not be
|
||||||
|
this.setElement(this._make_descriptive());
|
||||||
this.session = instance.connection;
|
this.session = instance.connection;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -702,20 +700,120 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
|
||||||
* key that references `this`.
|
* key that references `this`.
|
||||||
*/
|
*/
|
||||||
renderElement: function() {
|
renderElement: function() {
|
||||||
var rendered = null;
|
var $el;
|
||||||
if (this.template)
|
if (this.template) {
|
||||||
rendered = instance.web.qweb.render(this.template, {widget: this});
|
$el = $(_.str.trim(instance.web.qweb.render(
|
||||||
if (_.str.trim(rendered)) {
|
this.template, {widget: this})));
|
||||||
var elem = $(rendered);
|
} else {
|
||||||
this.$element.replaceWith(elem);
|
$el = this._make_descriptive();
|
||||||
this.$element = elem;
|
|
||||||
}
|
}
|
||||||
|
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() {
|
setElement: function (element) {
|
||||||
return this.$element.find.apply(this.$element,arguments);
|
// 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
|
* Informs the action manager to do an action. This supposes that
|
||||||
|
|
|
@ -19,15 +19,14 @@ instance.web.OldWidget = instance.web.Widget.extend({
|
||||||
this._super(parent);
|
this._super(parent);
|
||||||
this.element_id = element_id;
|
this.element_id = element_id;
|
||||||
this.element_id = this.element_id || _.uniqueId('widget-');
|
this.element_id = this.element_id || _.uniqueId('widget-');
|
||||||
|
|
||||||
var tmp = document.getElementById(this.element_id);
|
var tmp = document.getElementById(this.element_id);
|
||||||
this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName));
|
this.setElement(tmp || this._make_descriptive());
|
||||||
},
|
},
|
||||||
renderElement: function() {
|
renderElement: function() {
|
||||||
var rendered = this.render();
|
var rendered = this.render();
|
||||||
if (rendered) {
|
if (rendered) {
|
||||||
var elem = $(rendered);
|
this.replaceElement($(rendered));
|
||||||
this.$element.replaceWith(elem);
|
|
||||||
this.$element = elem;
|
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
@ -416,12 +415,12 @@ instance.web.Bus = instance.web.Class.extend(instance.web.EventDispatcherMixin,
|
||||||
// check gtk bindings
|
// check gtk bindings
|
||||||
// http://unixpapa.com/js/key.html
|
// http://unixpapa.com/js/key.html
|
||||||
_.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) {
|
_.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) {
|
||||||
$('html').on(evtype, self, function(ev) {
|
$('html').on(evtype, function(ev) {
|
||||||
self.trigger(evtype, ev);
|
self.trigger(evtype, ev);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
_.each('resize,scroll'.split(','), function(evtype) {
|
_.each('resize,scroll'.split(','), function(evtype) {
|
||||||
$(window).on(evtype, self, function(ev) {
|
$(window).on(evtype, function(ev) {
|
||||||
self.trigger(evtype, ev);
|
self.trigger(evtype, ev);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -516,7 +516,16 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
||||||
this.$element.addClass('oe_focused');
|
this.$element.addClass('oe_focused');
|
||||||
},
|
},
|
||||||
childBlurred: function () {
|
childBlurred: function () {
|
||||||
this.$element.removeClass('oe_focused');
|
var val = this.$element.val();
|
||||||
|
this.$element.val('');
|
||||||
|
var complete = this.$element.data('autocomplete');
|
||||||
|
if ((val && complete.term === undefined) || complete.previous !== undefined) {
|
||||||
|
throw new Error("new jquery.ui version altering implementation" +
|
||||||
|
" details relied on");
|
||||||
|
}
|
||||||
|
delete complete.term;
|
||||||
|
this.$element.removeClass('oe_focused')
|
||||||
|
.trigger('blur');
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -652,7 +661,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
|
||||||
null, _(this.select_for_drawer()).invoke(
|
null, _(this.select_for_drawer()).invoke(
|
||||||
'appendTo', this.$element.find('.oe_searchview_drawer')));
|
'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
|
// load defaults
|
||||||
var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
|
var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
|
||||||
|
@ -852,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,
|
template: null,
|
||||||
/**
|
/**
|
||||||
* Root class of all search widgets
|
* Root class of all search widgets
|
||||||
*
|
*
|
||||||
* @constructs instance.web.search.Widget
|
* @constructs instance.web.search.Widget
|
||||||
* @extends instance.web.OldWidget
|
* @extends instance.web.Widget
|
||||||
*
|
*
|
||||||
* @param view the ancestor view of this widget
|
* @param view the ancestor view of this widget
|
||||||
*/
|
*/
|
||||||
|
@ -1669,42 +1678,39 @@ instance.web.search.Filters = instance.web.search.Input.extend({
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
instance.web.search.AddToDashboard = instance.web.Widget.extend({
|
instance.web.search.AddToReporting = instance.web.Widget.extend({
|
||||||
template: 'SearchView.addtodashboard',
|
template: 'SearchView.addtoreporting',
|
||||||
_in_drawer: true,
|
_in_drawer: true,
|
||||||
start: function () {
|
start: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.data_loaded = $.Deferred();
|
|
||||||
this.dashboard_data =[];
|
|
||||||
this.$element
|
this.$element
|
||||||
.on('click', 'h4', this.proxy('show_option'))
|
.on('click', 'h4', this.proxy('show_option'))
|
||||||
.on('submit', 'form', function (e) {
|
.on('submit', 'form', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
self.add_dashboard();
|
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(){
|
load_data:function(){
|
||||||
if (!instance.webclient) { return $.Deferred().reject(); }
|
if (!instance.webclient) { return $.Deferred().reject(); }
|
||||||
var self = this,dashboard_menu = instance.webclient.menu.data.data.children;
|
var 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']);
|
return new instance.web.Model('ir.model.data')
|
||||||
var map_data = function(result){
|
.query(['res_id'])
|
||||||
_.detect(dashboard_menu, function(dash){
|
.filter([['name','=','menu_reporting_dashboard']])
|
||||||
var id = _.pluck(dash.children, "id"),indexof = _.indexOf(id, result.res_id);
|
.first().pipe(function (result) {
|
||||||
if(indexof !== -1){
|
var menu = _(dashboard_menu).chain()
|
||||||
self.dashboard_data = dash.children[indexof].children
|
.pluck('children')
|
||||||
self.data_loaded.resolve();
|
.flatten(true)
|
||||||
return;
|
.find(function (child) { return child.id === result.res_id; })
|
||||||
}
|
.value();
|
||||||
});
|
return menu ? menu.children : [];
|
||||||
};
|
});
|
||||||
return ir_model_data._execute().done(function(result){map_data(result[0])});
|
|
||||||
},
|
},
|
||||||
|
render_data: function(dashboard_choices){
|
||||||
render_data: function(){
|
var selection = instance.web.qweb.render(
|
||||||
var self = this;
|
"SearchView.addtoreporting.selection", {
|
||||||
var selection = instance.web.qweb.render("SearchView.addtodashboard.selection",{selections:this.dashboard_data});
|
selections: dashboard_choices});
|
||||||
this.$element.find("input").before(selection)
|
this.$("input").before(selection)
|
||||||
},
|
},
|
||||||
add_dashboard:function(){
|
add_dashboard:function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -1712,11 +1718,11 @@ instance.web.search.AddToDashboard = instance.web.Widget.extend({
|
||||||
var view_parent = this.getParent().getParent();
|
var view_parent = this.getParent().getParent();
|
||||||
if (! view_parent.action || ! this.$element.find("select").val())
|
if (! view_parent.action || ! this.$element.find("select").val())
|
||||||
return this.do_warn("Can't find dashboard action");
|
return this.do_warn("Can't find dashboard action");
|
||||||
data = getParent.build_search_data(),
|
var data = getParent.build_search_data();
|
||||||
context = new instance.web.CompoundContext(getParent.dataset.get_context() || []),
|
var context = new instance.web.CompoundContext(getParent.dataset.get_context() || []);
|
||||||
domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []);
|
var domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []);
|
||||||
_.each(data.contexts, function(x) {context.add(x);});
|
_.each(data.contexts, context.add, context);
|
||||||
_.each(data.domains, function(x) {domain.add(x);});
|
_.each(data.domains, domain.add, domain);
|
||||||
this.rpc('/web/searchview/add_to_dashboard', {
|
this.rpc('/web/searchview/add_to_dashboard', {
|
||||||
menu_id: this.$element.find("select").val(),
|
menu_id: this.$element.find("select").val(),
|
||||||
action_id: view_parent.action.id,
|
action_id: view_parent.action.id,
|
||||||
|
@ -1737,7 +1743,7 @@ instance.web.search.AddToDashboard = instance.web.Widget.extend({
|
||||||
this.$element.toggleClass('oe_opened');
|
this.$element.toggleClass('oe_opened');
|
||||||
if (! this.$element.hasClass('oe_opened'))
|
if (! this.$element.hasClass('oe_opened'))
|
||||||
return;
|
return;
|
||||||
this.$element.find("input").val(this.getParent().fields_view.name || "" );
|
this.$("input").val(this.getParent().fields_view.name || "" );
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,11 @@ instance.web.form.FieldManagerMixin = {
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.web.views.add('form', 'instance.web.FormView');
|
instance.web.views.add('form', 'instance.web.FormView');
|
||||||
|
/**
|
||||||
|
* Properties:
|
||||||
|
* - actual_mode: always "view", "edit" or "create". Read-only property. Determines
|
||||||
|
* the mode used by the view.
|
||||||
|
*/
|
||||||
instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerMixin, {
|
instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerMixin, {
|
||||||
/**
|
/**
|
||||||
* Indicates that this view is not searchable, and thus that no search
|
* Indicates that this view is not searchable, and thus that no search
|
||||||
|
@ -55,6 +60,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
* @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
|
* @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
|
||||||
*/
|
*/
|
||||||
init: function(parent, dataset, view_id, options) {
|
init: function(parent, dataset, view_id, options) {
|
||||||
|
var self = this;
|
||||||
this._super(parent);
|
this._super(parent);
|
||||||
this.set_default_options(options);
|
this.set_default_options(options);
|
||||||
this.dataset = dataset;
|
this.dataset = dataset;
|
||||||
|
@ -82,13 +88,22 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
this.__blur_timeout = null;
|
this.__blur_timeout = null;
|
||||||
this.rendering_engine = new instance.web.form.FormRenderingEngine(this);
|
this.rendering_engine = new instance.web.form.FormRenderingEngine(this);
|
||||||
this.qweb = null; // A QWeb instance will be created if the view is a QWeb template
|
this.qweb = null; // A QWeb instance will be created if the view is a QWeb template
|
||||||
|
self.set({actual_mode: self.options.initial_mode});
|
||||||
|
this.has_been_loaded.then(function() {
|
||||||
|
self.on("change:actual_mode", self, self.check_actual_mode);
|
||||||
|
self.check_actual_mode();
|
||||||
|
self.on("change:actual_mode", self, self.init_pager);
|
||||||
|
self.init_pager();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
destroy: function() {
|
destroy: function() {
|
||||||
_.each(this.get_widgets(), function(w) {
|
_.each(this.get_widgets(), function(w) {
|
||||||
w.off('focused blurred');
|
w.off('focused blurred');
|
||||||
w.destroy();
|
w.destroy();
|
||||||
});
|
});
|
||||||
this.$element.off('.formBlur');
|
if (this.$element) {
|
||||||
|
this.$element.off('.formBlur');
|
||||||
|
}
|
||||||
this._super();
|
this._super();
|
||||||
},
|
},
|
||||||
on_loaded: function(data) {
|
on_loaded: function(data) {
|
||||||
|
@ -125,17 +140,6 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
this.$buttons.on('click','.oe_form_button_save',this.on_button_save);
|
this.$buttons.on('click','.oe_form_button_save',this.on_button_save);
|
||||||
this.$buttons.on('click','.oe_form_button_cancel',this.on_button_cancel);
|
this.$buttons.on('click','.oe_form_button_cancel',this.on_button_cancel);
|
||||||
|
|
||||||
this.$pager = $(QWeb.render("FormView.pager", {'widget':self}));
|
|
||||||
if (this.options.$pager) {
|
|
||||||
this.$pager.appendTo(this.options.$pager);
|
|
||||||
} else {
|
|
||||||
this.$element.find('.oe_form_pager').replaceWith(this.$pager);
|
|
||||||
}
|
|
||||||
this.$pager.on('click','a[data-pager-action]',function() {
|
|
||||||
var action = $(this).data('pager-action');
|
|
||||||
self.on_pager_action(action);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$sidebar = this.options.$sidebar || this.$element.find('.oe_form_sidebar');
|
this.$sidebar = this.options.$sidebar || this.$element.find('.oe_form_sidebar');
|
||||||
if (!this.sidebar && this.options.$sidebar) {
|
if (!this.sidebar && this.options.$sidebar) {
|
||||||
this.sidebar = new instance.web.Sidebar(this);
|
this.sidebar = new instance.web.Sidebar(this);
|
||||||
|
@ -152,14 +156,12 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
|
|
||||||
// Add bounce effect on button 'Edit' when click on readonly page view.
|
// Add bounce effect on button 'Edit' when click on readonly page view.
|
||||||
this.$element.find(".oe_form_field, .oe_form_group_cell").on('click', function (e) {
|
this.$element.find(".oe_form_field, .oe_form_group_cell").on('click', function (e) {
|
||||||
if(self.get("mode") == "view") {
|
if(self.get("actual_mode") == "view") {
|
||||||
var $button = self.options.$buttons.find(".oe_form_button_edit");
|
var $button = self.options.$buttons.find(".oe_form_button_edit");
|
||||||
$button.wrap('<div>').css('margin-right','4px').addClass('oe_left oe_bounce');
|
$button.wrap('<div>').css('margin-right','4px').addClass('oe_left oe_bounce');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on("change:mode", this, this.switch_mode);
|
|
||||||
this.set({mode: this.options.initial_mode});
|
|
||||||
this.has_been_loaded.resolve();
|
this.has_been_loaded.resolve();
|
||||||
return $.when();
|
return $.when();
|
||||||
},
|
},
|
||||||
|
@ -267,8 +269,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
if (this.$pager) {
|
if (this.$pager) {
|
||||||
this.$pager.show();
|
this.$pager.show();
|
||||||
}
|
}
|
||||||
this.$element.show().css('visibility', 'hidden');
|
|
||||||
this.$element.add(this.$buttons).removeClass('oe_form_dirty');
|
this.$element.add(this.$buttons).removeClass('oe_form_dirty');
|
||||||
|
this.$element.css('visibility', 'visible');
|
||||||
|
this._super();
|
||||||
|
|
||||||
var shown = this.has_been_loaded;
|
var shown = this.has_been_loaded;
|
||||||
if (options.reload !== false) {
|
if (options.reload !== false) {
|
||||||
|
@ -277,16 +280,17 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
// null index means we should start a new record
|
// null index means we should start a new record
|
||||||
return self.on_button_new();
|
return self.on_button_new();
|
||||||
}
|
}
|
||||||
return self.dataset.read_index(_.keys(self.fields_view.fields), {
|
var fields = _.keys(self.fields_view.fields);
|
||||||
context: { 'bin_size': true }
|
fields.push('display_name');
|
||||||
|
return self.dataset.read_index(fields, {
|
||||||
|
context: { 'bin_size': true, 'future_display_name' : true }
|
||||||
}).pipe(self.on_record_loaded);
|
}).pipe(self.on_record_loaded);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return shown.pipe(function() {
|
return shown.pipe(function() {
|
||||||
if (options.editable) {
|
if (options.editable) {
|
||||||
self.set({mode: "edit"});
|
self.to_edit_mode();
|
||||||
}
|
}
|
||||||
self.$element.css('visibility', 'visible');
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
do_hide: function () {
|
do_hide: function () {
|
||||||
|
@ -309,7 +313,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
return $.Deferred().reject();
|
return $.Deferred().reject();
|
||||||
}
|
}
|
||||||
this.datarecord = record;
|
this.datarecord = record;
|
||||||
this.set({ 'title' : record.id ? record.name : "New record" });
|
this._actualize_mode();
|
||||||
|
this.set({ 'title' : record.id ? record.display_name : "New record" });
|
||||||
|
|
||||||
if (this.qweb) {
|
if (this.qweb) {
|
||||||
this.kill_current_form();
|
this.kill_current_form();
|
||||||
|
@ -386,6 +391,24 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
this.reload();
|
this.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
init_pager: function() {
|
||||||
|
var self = this;
|
||||||
|
if (this.$pager)
|
||||||
|
this.$pager.remove();
|
||||||
|
if (this.get("actual_mode") === "create")
|
||||||
|
return;
|
||||||
|
this.$pager = $(QWeb.render("FormView.pager", {'widget':self}));
|
||||||
|
if (this.options.$pager) {
|
||||||
|
this.$pager.appendTo(this.options.$pager);
|
||||||
|
} else {
|
||||||
|
this.$element.find('.oe_form_pager').replaceWith(this.$pager);
|
||||||
|
}
|
||||||
|
this.$pager.on('click','a[data-pager-action]',function() {
|
||||||
|
var action = $(this).data('pager-action');
|
||||||
|
self.on_pager_action(action);
|
||||||
|
});
|
||||||
|
this.do_update_pager();
|
||||||
|
},
|
||||||
do_update_pager: function(hide_index) {
|
do_update_pager: function(hide_index) {
|
||||||
var index = hide_index ? '-' : this.dataset.index + 1;
|
var index = hide_index ? '-' : this.dataset.index + 1;
|
||||||
this.$pager.find('button').prop('disabled', this.dataset.ids.length < 2).end()
|
this.$pager.find('button').prop('disabled', this.dataset.ids.length < 2).end()
|
||||||
|
@ -585,9 +608,32 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
return $.Deferred().reject();
|
return $.Deferred().reject();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
switch_mode: function() {
|
/**
|
||||||
|
* Ask the view to switch to view mode if possible. The view may not do it
|
||||||
|
* if the current record is not yet saved. It will then stay in create mode.
|
||||||
|
*/
|
||||||
|
to_view_mode: function() {
|
||||||
|
this._actualize_mode("view");
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Ask the view to switch to edit mode if possible. The view may not do it
|
||||||
|
* if the current record is not yet saved. It will then stay in create mode.
|
||||||
|
*/
|
||||||
|
to_edit_mode: function() {
|
||||||
|
this._actualize_mode("edit");
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Reactualize actual_mode.
|
||||||
|
*/
|
||||||
|
_actualize_mode: function(switch_to) {
|
||||||
|
var mode = switch_to || this.get("actual_mode");
|
||||||
|
if (! this.datarecord.id)
|
||||||
|
mode = "create";
|
||||||
|
this.set({actual_mode: mode});
|
||||||
|
},
|
||||||
|
check_actual_mode: function(source, options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if(this.get("mode") == "view") {
|
if(this.get("actual_mode") === "view") {
|
||||||
self.$element.removeClass('oe_form_editable').addClass('oe_form_readonly');
|
self.$element.removeClass('oe_form_editable').addClass('oe_form_readonly');
|
||||||
self.$buttons.find('.oe_form_buttons_edit').hide();
|
self.$buttons.find('.oe_form_buttons_edit').hide();
|
||||||
self.$buttons.find('.oe_form_buttons_view').show();
|
self.$buttons.find('.oe_form_buttons_view').show();
|
||||||
|
@ -605,11 +651,12 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
});
|
});
|
||||||
var fields_order = self.fields_order.slice(0);
|
var fields_order = self.fields_order.slice(0);
|
||||||
if (self.default_focus_field) {
|
if (self.default_focus_field) {
|
||||||
fields_order.unshift(self.default_focus_field);
|
fields_order.unshift(self.default_focus_field.name);
|
||||||
}
|
}
|
||||||
for (var i = 0; i < fields_order.length; i += 1) {
|
for (var i = 0; i < fields_order.length; i += 1) {
|
||||||
var field = self.fields[fields_order[i]];
|
var field = self.fields[fields_order[i]];
|
||||||
if (!field.get('effective_invisible') && !field.get('effective_readonly') && field.focus() !== false) {
|
if (!field.get('effective_invisible') && !field.get('effective_readonly')) {
|
||||||
|
field.focus();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -618,19 +665,19 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
on_button_save: function() {
|
on_button_save: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
return this.do_save().then(function(result) {
|
return this.do_save().then(function(result) {
|
||||||
self.set({mode: "view"});
|
self.to_view_mode();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
on_button_cancel: function(event) {
|
on_button_cancel: function(event) {
|
||||||
if (this.can_be_discarded()) {
|
if (this.can_be_discarded()) {
|
||||||
this.set({mode: "view"});
|
this.to_view_mode();
|
||||||
this.on_record_loaded(this.datarecord);
|
this.on_record_loaded(this.datarecord);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
on_button_new: function() {
|
on_button_new: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.set({mode: "edit"});
|
this.to_edit_mode();
|
||||||
return $.when(this.has_been_loaded).pipe(function() {
|
return $.when(this.has_been_loaded).pipe(function() {
|
||||||
if (self.can_be_discarded()) {
|
if (self.can_be_discarded()) {
|
||||||
return self.load_defaults();
|
return self.load_defaults();
|
||||||
|
@ -638,7 +685,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
on_button_edit: function() {
|
on_button_edit: function() {
|
||||||
return this.set({mode: "edit"});
|
return this.to_edit_mode();
|
||||||
},
|
},
|
||||||
on_button_create: function() {
|
on_button_create: function() {
|
||||||
this.dataset.index = null;
|
this.dataset.index = null;
|
||||||
|
@ -651,7 +698,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) {
|
self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) {
|
||||||
return self.on_created({ result : new_id });
|
return self.on_created({ result : new_id });
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return self.set({mode: "edit"});
|
return self.to_edit_mode();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
def.resolve();
|
def.resolve();
|
||||||
});
|
});
|
||||||
|
@ -813,8 +860,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
if (self.dataset.index == null || self.dataset.index < 0) {
|
if (self.dataset.index == null || self.dataset.index < 0) {
|
||||||
return $.when(self.on_button_new());
|
return $.when(self.on_button_new());
|
||||||
} else {
|
} else {
|
||||||
return self.dataset.read_index(_.keys(self.fields_view.fields), {
|
var fields = _.keys(self.fields_view.fields);
|
||||||
context : { 'bin_size' : true }
|
fields.push('display_name');
|
||||||
|
return self.dataset.read_index(fields, {
|
||||||
|
context : { 'bin_size' : true, 'future_display_name' : true }
|
||||||
}).pipe(self.on_record_loaded);
|
}).pipe(self.on_record_loaded);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -944,7 +993,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
register_field: function(field, name) {
|
register_field: function(field, name) {
|
||||||
this.fields[name] = field;
|
this.fields[name] = field;
|
||||||
this.fields_order.push(name);
|
this.fields_order.push(name);
|
||||||
if (field.node.attrs.default_focus == '1') {
|
if (JSON.parse(field.node.attrs.default_focus || "0")) {
|
||||||
this.default_focus_field = field;
|
this.default_focus_field = field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -966,7 +1015,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
|
||||||
return this.fields_view.fields[field_name];
|
return this.fields_view.fields[field_name];
|
||||||
},
|
},
|
||||||
is_create_mode: function() {
|
is_create_mode: function() {
|
||||||
return !this.datarecord.id;
|
return this.get("actual_mode") === "create";
|
||||||
},
|
},
|
||||||
open_translate_dialog: function(field) {
|
open_translate_dialog: function(field) {
|
||||||
return this._super(field);
|
return this._super(field);
|
||||||
|
@ -1650,7 +1699,7 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
|
||||||
this._super(view, node);
|
this._super(view, node);
|
||||||
this.force_disabled = false;
|
this.force_disabled = false;
|
||||||
this.string = (this.node.attrs.string || '').replace(/_/g, '');
|
this.string = (this.node.attrs.string || '').replace(/_/g, '');
|
||||||
if (this.node.attrs.default_focus == '1') {
|
if (JSON.parse(this.node.attrs.default_focus || "0")) {
|
||||||
// TODO fme: provide enter key binding to widgets
|
// TODO fme: provide enter key binding to widgets
|
||||||
this.view.default_focus_button = this;
|
this.view.default_focus_button = this;
|
||||||
}
|
}
|
||||||
|
@ -2019,7 +2068,7 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we
|
||||||
return this.get('value') === '' || this._super();
|
return this.get('value') === '' || this._super();
|
||||||
},
|
},
|
||||||
focus: function() {
|
focus: function() {
|
||||||
this.delay_focus(this.$element.find('input:first'));
|
this.$element.find('input:first')[0].focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2992,6 +3041,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
|
||||||
}
|
}
|
||||||
if(view.view_type === "list") {
|
if(view.view_type === "list") {
|
||||||
_.extend(view.options, {
|
_.extend(view.options, {
|
||||||
|
addable: null,
|
||||||
selectable: self.multi_selection,
|
selectable: self.multi_selection,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
import_enabled: false,
|
import_enabled: false,
|
||||||
|
@ -2999,7 +3049,6 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
|
||||||
});
|
});
|
||||||
if (self.get("effective_readonly")) {
|
if (self.get("effective_readonly")) {
|
||||||
_.extend(view.options, {
|
_.extend(view.options, {
|
||||||
addable: null,
|
|
||||||
deletable: null,
|
deletable: null,
|
||||||
reorderable: false,
|
reorderable: false,
|
||||||
});
|
});
|
||||||
|
@ -3029,7 +3078,6 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
|
||||||
this.views = views;
|
this.views = views;
|
||||||
|
|
||||||
this.viewmanager = new instance.web.form.One2ManyViewManager(this, this.dataset, views, {});
|
this.viewmanager = new instance.web.form.One2ManyViewManager(this, this.dataset, views, {});
|
||||||
this.viewmanager.$element.addClass("oe_view_manager_one2many");
|
|
||||||
this.viewmanager.o2m = self;
|
this.viewmanager.o2m = self;
|
||||||
var once = $.Deferred().then(function() {
|
var once = $.Deferred().then(function() {
|
||||||
self.init_form_last_update.resolve();
|
self.init_form_last_update.resolve();
|
||||||
|
@ -3424,6 +3472,34 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
instance.web.form.One2ManyList = instance.web.ListView.List.extend({
|
||||||
|
pad_table_to: function (count) {
|
||||||
|
this._super(count > 0 ? count - 1 : 0);
|
||||||
|
|
||||||
|
// magical invocation of wtf does that do
|
||||||
|
if (this.view.o2m.get('effective_readonly')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var columns = _(this.columns).filter(function (column) {
|
||||||
|
return column.invisible !== '1';
|
||||||
|
}).length;
|
||||||
|
if (this.options.selectable) { columns++; }
|
||||||
|
if (this.options.deletable) { columns++; }
|
||||||
|
var $cell = $('<td>', {
|
||||||
|
colspan: columns,
|
||||||
|
'class': 'oe_form_field_one2many_list_row_add'
|
||||||
|
}).text(_t("Add a row"))
|
||||||
|
.click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
self.view.do_add_record();
|
||||||
|
});
|
||||||
|
this.$current.append(
|
||||||
|
$('<tr>').append($cell))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
instance.web.form.One2ManyFormView = instance.web.FormView.extend({
|
instance.web.form.One2ManyFormView = instance.web.FormView.extend({
|
||||||
form_template: 'One2Many.formview',
|
form_template: 'One2Many.formview',
|
||||||
|
@ -3551,12 +3627,13 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
|
||||||
render_value: function() {
|
render_value: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context());
|
var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context());
|
||||||
|
var values = self.get("value")
|
||||||
var handle_names = function(data) {
|
var handle_names = function(data) {
|
||||||
var indexed = {};
|
var indexed = {};
|
||||||
_.each(data, function(el) {
|
_.each(data, function(el) {
|
||||||
indexed[el[0]] = el;
|
indexed[el[0]] = el;
|
||||||
});
|
});
|
||||||
data = _.map(self.get("value"), function(el) { return indexed[el]; });
|
data = _.map(values, function(el) { return indexed[el]; });
|
||||||
if (! self.get("effective_readonly")) {
|
if (! self.get("effective_readonly")) {
|
||||||
self.tags.containerElement().children().remove();
|
self.tags.containerElement().children().remove();
|
||||||
$("textarea", self.$element).css("padding-left", "3px");
|
$("textarea", self.$element).css("padding-left", "3px");
|
||||||
|
@ -3565,8 +3642,8 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
|
||||||
self.$element.html(QWeb.render("FieldMany2ManyTag", {elements: data}));
|
self.$element.html(QWeb.render("FieldMany2ManyTag", {elements: data}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (! self.get('values') || self.get('values').length > 0) {
|
if (! values || values.length > 0) {
|
||||||
this._display_orderer.add(dataset.name_get(self.get("value"))).then(handle_names);
|
this._display_orderer.add(dataset.name_get(values)).then(handle_names);
|
||||||
} else {
|
} else {
|
||||||
handle_names([]);
|
handle_names([]);
|
||||||
}
|
}
|
||||||
|
@ -4228,7 +4305,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
|
||||||
this.selection.view = this.view;
|
this.selection.view = this.view;
|
||||||
this.selection.set({force_readonly: this.get('effective_readonly')});
|
this.selection.set({force_readonly: this.get('effective_readonly')});
|
||||||
this.selection.on("change:value", this, this.on_selection_changed);
|
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.renderElement();
|
||||||
this.selection.start();
|
this.selection.start();
|
||||||
this.selection
|
this.selection
|
||||||
|
@ -4241,7 +4318,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
|
||||||
this.m2o.view = this.view;
|
this.m2o.view = this.view;
|
||||||
this.m2o.set({force_readonly: this.get("effective_readonly")});
|
this.m2o.set({force_readonly: this.get("effective_readonly")});
|
||||||
this.m2o.on("change:value", this, this.data_changed);
|
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.renderElement();
|
||||||
this.m2o.start();
|
this.m2o.start();
|
||||||
this.m2o
|
this.m2o
|
||||||
|
|
|
@ -376,6 +376,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
|
||||||
|
|
||||||
var total = dataset.size();
|
var total = dataset.size();
|
||||||
var limit = this.limit() || total;
|
var limit = this.limit() || total;
|
||||||
|
this.$pager.toggle(total !== 0);
|
||||||
this.$pager.toggleClass('oe_list_pager_single_page', (total <= limit));
|
this.$pager.toggleClass('oe_list_pager_single_page', (total <= limit));
|
||||||
var spager = '-';
|
var spager = '-';
|
||||||
if (total) {
|
if (total) {
|
||||||
|
@ -932,7 +933,6 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
|
||||||
'[data-id=' + record.get('id') + ']');
|
'[data-id=' + record.get('id') + ']');
|
||||||
var index = $row.data('index');
|
var index = $row.data('index');
|
||||||
$row.remove();
|
$row.remove();
|
||||||
self.refresh_zebra(index);
|
|
||||||
},
|
},
|
||||||
'reset': function () { return self.on_records_reset(); },
|
'reset': function () { return self.on_records_reset(); },
|
||||||
'change': function (event, record, attribute, value, old_value) {
|
'change': function (event, record, attribute, value, old_value) {
|
||||||
|
@ -967,8 +967,6 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
|
||||||
'[data-id=' + previous_record.get('id') + ']');
|
'[data-id=' + previous_record.get('id') + ']');
|
||||||
$new_row.insertAfter($previous_sibling);
|
$new_row.insertAfter($previous_sibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.refresh_zebra(index, 1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_(this.record_callbacks).each(function (callback, event) {
|
_(this.record_callbacks).each(function (callback, event) {
|
||||||
|
@ -1108,7 +1106,6 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
|
||||||
this.$current
|
this.$current
|
||||||
.children('tr:not([data-id])').remove().end()
|
.children('tr:not([data-id])').remove().end()
|
||||||
.append(new Array(count - this.records.length + 1).join(row));
|
.append(new Array(count - this.records.length + 1).join(row));
|
||||||
this.refresh_zebra(this.records.length);
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Gets the ids of all currently selected records, if any
|
* Gets the ids of all currently selected records, if any
|
||||||
|
@ -1182,25 +1179,6 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
|
||||||
render_cell: function () {
|
render_cell: function () {
|
||||||
return self.render_cell.apply(self, arguments); }
|
return self.render_cell.apply(self, arguments); }
|
||||||
});
|
});
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Fixes fixes the even/odd classes
|
|
||||||
*
|
|
||||||
* @param {Number} [from_index] index from which to resequence
|
|
||||||
* @param {Number} [offset = 0] selection offset for DOM, in case there are rows to ignore in the table
|
|
||||||
*/
|
|
||||||
refresh_zebra: function (from_index, offset) {
|
|
||||||
offset = offset || 0;
|
|
||||||
from_index = from_index || 0;
|
|
||||||
var dom_offset = offset + from_index;
|
|
||||||
var sel = dom_offset ? ':gt(' + (dom_offset - 1) + ')' : null;
|
|
||||||
this.$current.children(sel).each(function (i, e) {
|
|
||||||
var index = from_index + i;
|
|
||||||
// reset record-index accelerators on rows and even/odd
|
|
||||||
var even = index%2 === 0;
|
|
||||||
$(e).toggleClass('even', even)
|
|
||||||
.toggleClass('odd', !even);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.web.ListView.Groups# */{
|
instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.web.ListView.Groups# */{
|
||||||
|
@ -1533,8 +1511,6 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
|
||||||
}(dataset, record.get('id'), seq));
|
}(dataset, record.get('id'), seq));
|
||||||
record.set('sequence', seq);
|
record.set('sequence', seq);
|
||||||
}
|
}
|
||||||
|
|
||||||
list.refresh_zebra();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -96,9 +96,6 @@ openerp.web.list_editable = function (instance) {
|
||||||
},
|
},
|
||||||
on_loaded: function (data, grouped) {
|
on_loaded: function (data, grouped) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (this.editor) {
|
|
||||||
this.editor.destroy();
|
|
||||||
}
|
|
||||||
// tree/@editable takes priority on everything else if present.
|
// tree/@editable takes priority on everything else if present.
|
||||||
var result = this._super(data, grouped);
|
var result = this._super(data, grouped);
|
||||||
if (this.editable()) {
|
if (this.editable()) {
|
||||||
|
@ -118,6 +115,7 @@ openerp.web.list_editable = function (instance) {
|
||||||
self.start_edition();
|
self.start_edition();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.editor.destroy();
|
||||||
// Editor is not restartable due to formview not being
|
// Editor is not restartable due to formview not being
|
||||||
// restartable
|
// restartable
|
||||||
this.editor = this.make_editor();
|
this.editor = this.make_editor();
|
||||||
|
@ -425,26 +423,64 @@ openerp.web.list_editable = function (instance) {
|
||||||
keyup_ESCAPE: function () {
|
keyup_ESCAPE: function () {
|
||||||
return this.cancel_edition();
|
return this.cancel_edition();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Gets the selection range (start, end) for the provided element,
|
||||||
|
* returns ``null`` if it can't get a range.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
_text_selection_range: function (el) {
|
_text_selection_range: function (el) {
|
||||||
if (el.selectionStart !== undefined) {
|
var selectionStart;
|
||||||
|
try {
|
||||||
|
selectionStart = el.selectionStart;
|
||||||
|
} catch (e) {
|
||||||
|
// radio or checkbox throw on selectionStart access
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (selectionStart !== undefined) {
|
||||||
return {
|
return {
|
||||||
start: el.selectionStart,
|
start: selectionStart,
|
||||||
end: el.selectionEnd
|
end: el.selectionEnd
|
||||||
};
|
};
|
||||||
} else if(document.body.createTextRange) {
|
} else if (document.body.createTextRange) {
|
||||||
throw new Error("Implement text range handling for MSIE");
|
throw new Error("Implement text range handling for MSIE");
|
||||||
var sel = document.body.createTextRange();
|
var sel = document.body.createTextRange();
|
||||||
if (sel.parentElement() === el) {
|
if (sel.parentElement() === el) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Element without selection ranges (select, div/@contenteditable)
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
_text_cursor: function (el) {
|
_text_cursor: function (el) {
|
||||||
var selection = this._text_selection_range(el);
|
var selection = this._text_selection_range(el);
|
||||||
if (selection.start !== selection.end) {
|
if (!selection) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return selection.start;
|
if (selection.start !== selection.end) {
|
||||||
|
return {position: null, collapsed: false};
|
||||||
|
}
|
||||||
|
return {position: selection.start, collapsed: true};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks if the cursor is at the start of the provided el
|
||||||
|
*
|
||||||
|
* @param {HTMLInputElement | HTMLTextAreaElement}
|
||||||
|
* @returns {Boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_at_start: function (cursor, el) {
|
||||||
|
return cursor.collapsed && (cursor.position === 0);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks if the cursor is at the end of the provided el
|
||||||
|
*
|
||||||
|
* @param {HTMLInputElement | HTMLTextAreaElement}
|
||||||
|
* @returns {Boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_at_end: function (cursor, el) {
|
||||||
|
return cursor.collapsed && (cursor.position === el.value.length);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @param DOMEvent event
|
* @param DOMEvent event
|
||||||
|
@ -454,10 +490,11 @@ openerp.web.list_editable = function (instance) {
|
||||||
*/
|
*/
|
||||||
_key_move_record: function (event, record_direction, is_valid_move) {
|
_key_move_record: function (event, record_direction, is_valid_move) {
|
||||||
if (!this.editor.is_editing('edit')) { return $.when(); }
|
if (!this.editor.is_editing('edit')) { return $.when(); }
|
||||||
// FIXME: assumes editable widgets are input-type elements
|
var cursor = this._text_cursor(event.target);
|
||||||
var index = this._text_cursor(event.target);
|
// if text-based input (has a cursor)
|
||||||
// If selecting or not at the start of the input
|
// and selecting (not collapsed) or not at a field boundary
|
||||||
if (!is_valid_move(event.target, index)) { return $.when(); }
|
// don't move to the next record
|
||||||
|
if (cursor && !is_valid_move(event.target, cursor)) { return $.when(); }
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var source_field = $(event.target).closest('[data-fieldname]')
|
var source_field = $(event.target).closest('[data-fieldname]')
|
||||||
|
@ -466,13 +503,15 @@ openerp.web.list_editable = function (instance) {
|
||||||
|
|
||||||
},
|
},
|
||||||
keydown_UP: function (e) {
|
keydown_UP: function (e) {
|
||||||
return this._key_move_record(e, 'pred', function (el, index) {
|
var self = this;
|
||||||
return index === 0;
|
return this._key_move_record(e, 'pred', function (el, cursor) {
|
||||||
|
return self._at_start(cursor, el);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
keydown_DOWN: function (e) {
|
keydown_DOWN: function (e) {
|
||||||
return this._key_move_record(e, 'succ', function (el, index) {
|
var self = this;
|
||||||
return index === el.value.length;
|
return this._key_move_record(e, 'succ', function (el, cursor) {
|
||||||
|
return self._at_end(cursor, el);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -480,8 +519,8 @@ openerp.web.list_editable = function (instance) {
|
||||||
// If the cursor is at the beginning of the field
|
// If the cursor is at the beginning of the field
|
||||||
var source_field = $(e.target).closest('[data-fieldname]')
|
var source_field = $(e.target).closest('[data-fieldname]')
|
||||||
.attr('data-fieldname');
|
.attr('data-fieldname');
|
||||||
var index = this._text_cursor(e.target);
|
var cursor = this._text_cursor(e.target);
|
||||||
if (index !== 0) { return $.when(); }
|
if (cursor && !this._at_start(cursor, e.target)) { return $.when(); }
|
||||||
|
|
||||||
var fields_order = this.editor.form.fields_order;
|
var fields_order = this.editor.form.fields_order;
|
||||||
var field_index = _(fields_order).indexOf(source_field);
|
var field_index = _(fields_order).indexOf(source_field);
|
||||||
|
@ -504,8 +543,8 @@ openerp.web.list_editable = function (instance) {
|
||||||
// looking for new fields at the right
|
// looking for new fields at the right
|
||||||
var source_field = $(e.target).closest('[data-fieldname]')
|
var source_field = $(e.target).closest('[data-fieldname]')
|
||||||
.attr('data-fieldname');
|
.attr('data-fieldname');
|
||||||
var index = this._text_cursor(e.target);
|
var cursor = this._text_cursor(e.target);
|
||||||
if (index !== e.target.value.length) { return $.when(); }
|
if (cursor && !this._at_end(cursor, e.target)) { return $.when(); }
|
||||||
|
|
||||||
var fields_order = this.editor.form.fields_order;
|
var fields_order = this.editor.form.fields_order;
|
||||||
var field_index = _(fields_order).indexOf(source_field);
|
var field_index = _(fields_order).indexOf(source_field);
|
||||||
|
|
|
@ -214,21 +214,30 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
||||||
this.dialog_stop();
|
this.dialog_stop();
|
||||||
this.clear_breadcrumbs();
|
this.clear_breadcrumbs();
|
||||||
},
|
},
|
||||||
ir_actions_act_window: function (action, on_close) {
|
|
||||||
var self = this;
|
do_ir_actions_common: function(action, on_close) {
|
||||||
if (_(['base.module.upgrade', 'base.setup.installer'])
|
var self = this, klass, widget, add_breadcrumb;
|
||||||
.contains(action.res_model)) {
|
if (action.type === 'ir.actions.client') {
|
||||||
var old_close = on_close;
|
var ClientWidget = instance.web.client_actions.get_object(action.tag);
|
||||||
on_close = function () {
|
widget = new ClientWidget(this, action.params);
|
||||||
instance.webclient.do_reload().then(old_close);
|
klass = 'oe_act_client';
|
||||||
};
|
add_breadcrumb = function() {
|
||||||
|
self.push_breadcrumb({
|
||||||
|
widget: widget,
|
||||||
|
title: action.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
widget = new instance.web.ViewManagerAction(this, action);
|
||||||
|
klass = 'oe_act_window';
|
||||||
|
add_breadcrumb = widget.proxy('add_breadcrumb');
|
||||||
}
|
}
|
||||||
if (action.target === 'new') {
|
if (action.target === 'new') {
|
||||||
if (this.dialog === null) {
|
if (this.dialog === null) {
|
||||||
// These buttons will be overwrited by <footer> if any
|
// These buttons will be overwrited by <footer> if any
|
||||||
this.dialog = new instance.web.Dialog(this, {
|
this.dialog = new instance.web.Dialog(this, {
|
||||||
buttons: { "Close": function() { $(this).dialog("close"); }},
|
buttons: { "Close": function() { $(this).dialog("close"); }},
|
||||||
dialogClass: 'oe_act_window'
|
dialogClass: klass
|
||||||
});
|
});
|
||||||
if(on_close)
|
if(on_close)
|
||||||
this.dialog.on_close.add(on_close);
|
this.dialog.on_close.add(on_close);
|
||||||
|
@ -236,7 +245,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
||||||
this.dialog_widget.destroy();
|
this.dialog_widget.destroy();
|
||||||
}
|
}
|
||||||
this.dialog.dialog_title = action.name;
|
this.dialog.dialog_title = action.name;
|
||||||
this.dialog_widget = new instance.web.ViewManagerAction(this, action);
|
this.dialog_widget = widget;
|
||||||
this.dialog_widget.appendTo(this.dialog.$element);
|
this.dialog_widget.appendTo(this.dialog.$element);
|
||||||
this.dialog.open();
|
this.dialog.open();
|
||||||
} else {
|
} else {
|
||||||
|
@ -247,11 +256,34 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.inner_action = action;
|
this.inner_action = action;
|
||||||
var inner_widget = this.inner_widget = new instance.web.ViewManagerAction(this, action);
|
this.inner_widget = widget;
|
||||||
inner_widget.add_breadcrumb();
|
add_breadcrumb();
|
||||||
this.inner_widget.appendTo(this.$element);
|
this.inner_widget.appendTo(this.$element);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ir_actions_act_window: function (action, on_close) {
|
||||||
|
var self = this;
|
||||||
|
if (_(['base.module.upgrade', 'base.setup.installer'])
|
||||||
|
.contains(action.res_model)) {
|
||||||
|
var old_close = on_close;
|
||||||
|
on_close = function () {
|
||||||
|
instance.webclient.do_reload().then(old_close);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.target !== 'new') {
|
||||||
|
if(action.menu_id) {
|
||||||
|
this.dialog_stop();
|
||||||
|
return this.getParent().do_action(action, function () {
|
||||||
|
instance.webclient.menu.open_menu(action.menu_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.do_ir_actions_common(action, on_close);
|
||||||
|
},
|
||||||
|
ir_actions_client: function (action, on_close) {
|
||||||
|
return this.do_ir_actions_common(action, on_close);
|
||||||
|
},
|
||||||
ir_actions_act_window_close: function (action, on_closed) {
|
ir_actions_act_window_close: function (action, on_closed) {
|
||||||
if (!this.dialog && on_closed) {
|
if (!this.dialog && on_closed) {
|
||||||
on_closed();
|
on_closed();
|
||||||
|
@ -267,18 +299,6 @@ instance.web.ActionManager = instance.web.Widget.extend({
|
||||||
self.do_action(action, on_closed)
|
self.do_action(action, on_closed)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
ir_actions_client: function (action) {
|
|
||||||
this.dialog_stop();
|
|
||||||
var ClientWidget = instance.web.client_actions.get_object(action.tag);
|
|
||||||
this.inner_widget = new ClientWidget(this, action.params);
|
|
||||||
this.push_breadcrumb({
|
|
||||||
widget: this.inner_widget,
|
|
||||||
title: action.name
|
|
||||||
});
|
|
||||||
this.inner_action = action;
|
|
||||||
this.do_push_state({});
|
|
||||||
this.inner_widget.appendTo(this.$element);
|
|
||||||
},
|
|
||||||
ir_actions_report_xml: function(action, on_closed) {
|
ir_actions_report_xml: function(action, on_closed) {
|
||||||
var self = this;
|
var self = this;
|
||||||
instance.web.blockUI();
|
instance.web.blockUI();
|
||||||
|
@ -403,7 +423,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
||||||
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
|
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
|
||||||
.parent().addClass('active');
|
.parent().addClass('active');
|
||||||
|
|
||||||
$.when(view_promise).then(function () {
|
return $.when(view_promise).then(function () {
|
||||||
_.each(_.keys(self.views), function(view_name) {
|
_.each(_.keys(self.views), function(view_name) {
|
||||||
var controller = self.views[view_name].controller;
|
var controller = self.views[view_name].controller;
|
||||||
if (controller) {
|
if (controller) {
|
||||||
|
@ -425,7 +445,6 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return view_promise;
|
|
||||||
},
|
},
|
||||||
do_create_view: function(view_type) {
|
do_create_view: function(view_type) {
|
||||||
// Lazy loading of views
|
// Lazy loading of views
|
||||||
|
@ -485,6 +504,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
||||||
});
|
});
|
||||||
this.getParent().push_breadcrumb({
|
this.getParent().push_breadcrumb({
|
||||||
widget: this,
|
widget: this,
|
||||||
|
action: this.action,
|
||||||
show: function(index, $e) {
|
show: function(index, $e) {
|
||||||
var view_to_select = views[index];
|
var view_to_select = views[index];
|
||||||
self.$element.show();
|
self.$element.show();
|
||||||
|
@ -493,9 +513,27 @@ instance.web.ViewManager = instance.web.Widget.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
get_title: function() {
|
get_title: function() {
|
||||||
return _.map(views, function(v) {
|
var id;
|
||||||
return self.views[v].controller.get('title');
|
var currentIndex;
|
||||||
|
_.each(self.getParent().breadcrumbs, function(bc, i) {
|
||||||
|
if (bc.widget === self) {
|
||||||
|
currentIndex = i;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
var next = self.getParent().breadcrumbs.slice(currentIndex + 1)[0];
|
||||||
|
var titles = _.map(views, function(v) {
|
||||||
|
var controller = self.views[v].controller;
|
||||||
|
if (v === 'form') {
|
||||||
|
id = controller.datarecord.id;
|
||||||
|
}
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
return titles;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1136,7 +1174,7 @@ instance.web.View = instance.web.Widget.extend({
|
||||||
this.view_id = view_id;
|
this.view_id = view_id;
|
||||||
this.set_default_options(options);
|
this.set_default_options(options);
|
||||||
},
|
},
|
||||||
start: function() {
|
start: function () {
|
||||||
return this.load_view();
|
return this.load_view();
|
||||||
},
|
},
|
||||||
load_view: function() {
|
load_view: function() {
|
||||||
|
|
|
@ -518,25 +518,25 @@
|
||||||
<div class="oe_form_dropdown_section">
|
<div class="oe_form_dropdown_section">
|
||||||
<button class="oe_dropdown_toggle oe_dropdown_arrow">
|
<button class="oe_dropdown_toggle oe_dropdown_arrow">
|
||||||
<t t-esc="section.label"/>
|
<t t-esc="section.label"/>
|
||||||
<ul class="oe_dropdown_menu">
|
|
||||||
<li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
|
|
||||||
<a class="oe_sidebar_action_a" t-att-title="item.title" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank">
|
|
||||||
<t t-raw="item.label"/>
|
|
||||||
</a>
|
|
||||||
<a t-if="section.name == 'files'" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a>
|
|
||||||
</li>
|
|
||||||
<li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
|
|
||||||
<t t-call="HiddenInputFile">
|
|
||||||
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
|
|
||||||
<t t-set="fileupload_action">/web/binary/upload_attachment</t>
|
|
||||||
<input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/>
|
|
||||||
<input type="hidden" name="id" t-att-value="widget.model_id"/>
|
|
||||||
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
|
|
||||||
<span>Add...</span>
|
|
||||||
</t>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</button>
|
</button>
|
||||||
|
<ul class="oe_dropdown_menu">
|
||||||
|
<li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname">
|
||||||
|
<a class="oe_sidebar_action_a" t-att-title="item.title" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank">
|
||||||
|
<t t-raw="item.label"/>
|
||||||
|
</a>
|
||||||
|
<a t-if="section.name == 'files'" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a>
|
||||||
|
</li>
|
||||||
|
<li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
|
||||||
|
<t t-call="HiddenInputFile">
|
||||||
|
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
|
||||||
|
<t t-set="fileupload_action">/web/binary/upload_attachment</t>
|
||||||
|
<input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/>
|
||||||
|
<input type="hidden" name="id" t-att-value="widget.model_id"/>
|
||||||
|
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
|
||||||
|
<span>Add...</span>
|
||||||
|
</t>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
|
@ -645,10 +645,9 @@
|
||||||
<t t-name="ListView.rows" t-foreach="records.length" t-as="index">
|
<t t-name="ListView.rows" t-foreach="records.length" t-as="index">
|
||||||
<t t-call="ListView.row">
|
<t t-call="ListView.row">
|
||||||
<t t-set="record" t-value="records.at(index)"/>
|
<t t-set="record" t-value="records.at(index)"/>
|
||||||
<t t-set="row_parity" t-value="index_parity"/>
|
|
||||||
</t>
|
</t>
|
||||||
</t>
|
</t>
|
||||||
<tr t-name="ListView.row" t-att-class="row_parity"
|
<tr t-name="ListView.row"
|
||||||
t-att-data-id="record.get('id')"
|
t-att-data-id="record.get('id')"
|
||||||
t-att-style="view.style_for(record)">
|
t-att-style="view.style_for(record)">
|
||||||
<t t-set="asData" t-value="record.toForm().data"/>
|
<t t-set="asData" t-value="record.toForm().data"/>
|
||||||
|
@ -1444,18 +1443,18 @@
|
||||||
<div>
|
<div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div t-name="SearchView.addtodashboard" class="oe_searchview_dashboard">
|
<div t-name="SearchView.addtoreporting" class="oe_searchview_dashboard">
|
||||||
<h4>Add to Dashboard</h4>
|
<h4>Add to Reporting</h4>
|
||||||
<form>
|
<form>
|
||||||
<p><input placeholder ="Title of new Dashboard item" title = "Title of new Dashboard item" type="text"/></p>
|
<p><input placeholder="Title of new dashboard item"/></p>
|
||||||
<button class="oe_apply" type="submit">Save</button>
|
<button class="oe_apply" type="submit">Add</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<t t-name="SearchView.addtodashboard.selection">
|
<t t-name="SearchView.addtoreporting.selection">
|
||||||
<select title = "Select Dashboard to add this filter to">
|
<select>
|
||||||
<t t-foreach="selections" t-as="element">
|
<option t-foreach="selections" t-as="element"
|
||||||
<option t-att-value="element.id || element.res_id "><t t-esc="element.name"/></option>
|
t-att-value="element.id || element.res_id ">
|
||||||
</t>
|
<t t-esc="element.name"/></option>
|
||||||
</select>
|
</select>
|
||||||
</t>
|
</t>
|
||||||
<div t-name="SearchView.advanced" class="oe_searchview_advanced">
|
<div t-name="SearchView.advanced" class="oe_searchview_advanced">
|
||||||
|
|
|
@ -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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -72,7 +72,7 @@ $(document).ready(function () {
|
||||||
});
|
});
|
||||||
asyncTest('base-state', 2, function () {
|
asyncTest('base-state', 2, function () {
|
||||||
var e = new instance.web.list.Editor({
|
var e = new instance.web.list.Editor({
|
||||||
dataset: {},
|
dataset: {ids: []},
|
||||||
edition_view: function () {
|
edition_view: function () {
|
||||||
return makeFormView();
|
return makeFormView();
|
||||||
}
|
}
|
||||||
|
@ -240,8 +240,7 @@ $(document).ready(function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
|
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
|
||||||
var l = new instance.web.ListView({}, ds);
|
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||||
l.set_editable(true);
|
|
||||||
|
|
||||||
l.appendTo($fix)
|
l.appendTo($fix)
|
||||||
.pipe(l.proxy('reload_content'))
|
.pipe(l.proxy('reload_content'))
|
||||||
|
@ -305,8 +304,7 @@ $(document).ready(function () {
|
||||||
counter: 0,
|
counter: 0,
|
||||||
onEvent: function (e) { this.counter++; }
|
onEvent: function (e) { this.counter++; }
|
||||||
};
|
};
|
||||||
var l = new instance.web.ListView({}, ds);
|
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||||
l.set_editable(true);
|
|
||||||
l.on('edit:before edit:after', o, o.onEvent);
|
l.on('edit:before edit:after', o, o.onEvent);
|
||||||
l.appendTo($fix)
|
l.appendTo($fix)
|
||||||
.pipe(l.proxy('reload_content'))
|
.pipe(l.proxy('reload_content'))
|
||||||
|
@ -326,8 +324,7 @@ $(document).ready(function () {
|
||||||
asyncTest('edition events: cancelling', 3, function () {
|
asyncTest('edition events: cancelling', 3, function () {
|
||||||
var edit_after = false;
|
var edit_after = false;
|
||||||
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
|
var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]);
|
||||||
var l = new instance.web.ListView({}, ds);
|
var l = new instance.web.ListView({}, ds, false, {editable: 'top'});
|
||||||
l.set_editable(true);
|
|
||||||
l.on('edit:before', {}, function (e) {
|
l.on('edit:before', {}, function (e) {
|
||||||
e.cancel = true;
|
e.cancel = true;
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
|
|
||||||
<script src="/web/static/test/testing.js"></script>
|
<script src="/web/static/test/testing.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
QUnit.config.testTimeout = 500;
|
QUnit.config.testTimeout = 2000;
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body id="oe" class="openerp">
|
<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/rpc.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/test/evals.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/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>
|
<script type="text/javascript" src="/web/static/test/list-editable.js"></script>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Web Calendar",
|
"name": "Web Calendar",
|
||||||
"category": "Hidden",
|
"category": "Hidden",
|
||||||
"description":
|
"description":"""OpenERP Web Calendar view.""",
|
||||||
"""
|
|
||||||
OpenERP Web Calendar view.
|
|
||||||
""",
|
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"depends": ['web'],
|
"depends": ['web'],
|
||||||
"js": [
|
"js": [
|
||||||
|
|
|
@ -492,15 +492,17 @@ instance.web_calendar.Sidebar = instance.web.Widget.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
instance.web_calendar.SidebarFilter = instance.web.Widget.extend({
|
instance.web_calendar.SidebarFilter = instance.web.Widget.extend({
|
||||||
|
events: {
|
||||||
|
'change input:checkbox': 'on_filter_click'
|
||||||
|
},
|
||||||
init: function(parent, view) {
|
init: function(parent, view) {
|
||||||
this._super(parent);
|
this._super(parent);
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.$element.delegate('input:checkbox', 'change', this.on_filter_click);
|
|
||||||
},
|
},
|
||||||
on_events_loaded: function(filters) {
|
on_events_loaded: function(filters) {
|
||||||
var selected_filters = this.view.selected_filters.slice(0);
|
var selected_filters = this.view.selected_filters.slice(0);
|
||||||
this.$element.html(QWeb.render('CalendarView.sidebar.responsible', { filters: filters }));
|
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) {
|
if (_.indexOf(selected_filters, $(this).val()) > -1) {
|
||||||
$(this).click();
|
$(this).click();
|
||||||
}
|
}
|
||||||
|
@ -511,7 +513,7 @@ instance.web_calendar.SidebarFilter = instance.web.Widget.extend({
|
||||||
responsibles = [],
|
responsibles = [],
|
||||||
$e = $(e.target);
|
$e = $(e.target);
|
||||||
this.view.selected_filters = [];
|
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());
|
responsibles.push($(this).val());
|
||||||
self.view.selected_filters.push($(this).val());
|
self.view.selected_filters.push($(this).val());
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Web Dashboard",
|
"name": "Web Dashboard",
|
||||||
"category": "Hidden",
|
"category": "Hidden",
|
||||||
"description":
|
"description":"""OpenERP Web Dashboard view.""",
|
||||||
"""
|
|
||||||
OpenERP Web Dashboard view.
|
|
||||||
""",
|
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"depends": ['web'],
|
"depends": ['web'],
|
||||||
"js": [
|
"js": [
|
||||||
|
|
|
@ -226,6 +226,8 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderElement: function() {
|
renderElement: function() {
|
||||||
|
this._super();
|
||||||
|
|
||||||
var check = _.detect(this.node.children, function(column, column_index) {
|
var check = _.detect(this.node.children, function(column, column_index) {
|
||||||
return _.detect(column.children,function(element){
|
return _.detect(column.children,function(element){
|
||||||
return element.tag === "action"? element: false;
|
return element.tag === "action"? element: false;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name" : "OpenERP Web Diagram",
|
"name" : "OpenERP Web Diagram",
|
||||||
"category" : "Hidden",
|
"category" : "Hidden",
|
||||||
"description":'Openerp Web Diagram view',
|
"description":"""Openerp Web Diagram view.""",
|
||||||
"version" : "2.0",
|
"version" : "2.0",
|
||||||
"depends" : ["web"],
|
"depends" : ["web"],
|
||||||
"js": [
|
"js": [
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Web Gantt",
|
"name": "Web Gantt",
|
||||||
"category": "Hidden",
|
"category": "Hidden",
|
||||||
"description":
|
"description":"""OpenERP Web Gantt chart view.""",
|
||||||
"""
|
|
||||||
OpenERP Web Gantt chart view.
|
|
||||||
""",
|
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"depends": ['web'],
|
"depends": ['web'],
|
||||||
"js": [
|
"js": [
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "Graph Views",
|
"name": "Graph Views",
|
||||||
"category" : "Hidden",
|
"category" : "Hidden",
|
||||||
"description":"""Graph Views for Web Client
|
"description":"""
|
||||||
|
Graph Views for Web Client.
|
||||||
|
===========================
|
||||||
|
|
||||||
* Parse a <graph> view but allows changing dynamically the presentation
|
* Parse a <graph> view but allows changing dynamically the presentation
|
||||||
* Graph Types: pie, lines, areas, bars, radar
|
* Graph Types: pie, lines, areas, bars, radar
|
||||||
* Stacked/Not Stacked for areas and bars
|
* Stacked/Not Stacked for areas and bars
|
||||||
* Legends: top, inside (top/left), hidden
|
* Legends: top, inside (top/left), hidden
|
||||||
* Features: download as PNG or CSV, browse data grid, switch orientation
|
* Features: download as PNG or CSV, browse data grid, switch orientation
|
||||||
* Unlimited "Group By" levels (not stacked), two cross level analysis (stacked)
|
* Unlimited "Group By" levels (not stacked), two cross level analysis (stacked)
|
||||||
""",
|
""",
|
||||||
"version": "3.0",
|
"version": "3.0",
|
||||||
"depends": ['web'],
|
"depends": ['web'],
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Hello",
|
"name": "Hello",
|
||||||
"category": "Hidden",
|
"category": "Hidden",
|
||||||
"description":
|
"description":"""OpenERP Web example module.""",
|
||||||
"""
|
|
||||||
OpenERP Web example module.
|
|
||||||
""",
|
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"depends": [],
|
"depends": [],
|
||||||
"js": ["static/*/*.js", "static/*/js/*.js"],
|
"js": ["static/*/*.js", "static/*/js/*.js"],
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
{
|
{
|
||||||
"name" : "Base Kanban",
|
"name" : "Base Kanban",
|
||||||
"category": "Hidden",
|
"category": "Hidden",
|
||||||
"description":
|
"description":"""OpenERP Web kanban view.""",
|
||||||
"""
|
|
||||||
OpenERP Web kanban view.
|
|
||||||
""",
|
|
||||||
"version" : "2.0",
|
"version" : "2.0",
|
||||||
"depends" : ["web"],
|
"depends" : ["web"],
|
||||||
"js": [
|
"js": [
|
||||||
|
|
|
@ -39,6 +39,9 @@
|
||||||
.openerp .oe_kanban_view .oe_kanban_groups {
|
.openerp .oe_kanban_view .oe_kanban_groups {
|
||||||
height: inherit;
|
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 {
|
.openerp .oe_kanban_view .oe_kanban_header:hover .oe_dropdown_kanban {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +67,7 @@
|
||||||
.openerp .oe_kanban_view .oe_kanban_group_header.oe_kanban_no_group {
|
.openerp .oe_kanban_view .oe_kanban_group_header.oe_kanban_no_group {
|
||||||
padding: 0px;
|
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;
|
background: #f0eeee;
|
||||||
border-left: 1px solid #f0f8f8;
|
border-left: 1px solid #f0f8f8;
|
||||||
border-right: 1px solid #b9b9b9;
|
border-right: 1px solid #b9b9b9;
|
||||||
|
@ -188,7 +191,7 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 2px 4px;
|
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;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
.openerp .oe_kanban_view .oe_kanban_gravatar {
|
.openerp .oe_kanban_view .oe_kanban_gravatar {
|
||||||
|
@ -239,13 +242,13 @@
|
||||||
clear: both;
|
clear: both;
|
||||||
text-align: center;
|
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%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.openerp .oe_kanban_view .oe_kanban_ungrouped {
|
.openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_column {
|
||||||
background: white;
|
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;
|
float: left;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
// KanbanGroups {{{
|
// KanbanGroups {{{
|
||||||
.oe_kanban_groups
|
.oe_kanban_groups
|
||||||
height: inherit
|
height: inherit
|
||||||
|
&.oe_kanban_ungrouped .oe_kanban_groups
|
||||||
|
width: 100%
|
||||||
.oe_kanban_header
|
.oe_kanban_header
|
||||||
&:hover
|
&:hover
|
||||||
.oe_dropdown_kanban
|
.oe_dropdown_kanban
|
||||||
|
@ -86,7 +88,7 @@
|
||||||
.oe_kanban_group_header.oe_kanban_no_group
|
.oe_kanban_group_header.oe_kanban_no_group
|
||||||
padding: 0px
|
padding: 0px
|
||||||
|
|
||||||
.oe_kanban_column.oe_kanban_grouped, .oe_kanban_group_header
|
&.oe_kanban_grouped .oe_kanban_column, .oe_kanban_group_header
|
||||||
background: #f0eeee
|
background: #f0eeee
|
||||||
border-left: 1px solid #f0f8f8
|
border-left: 1px solid #f0f8f8
|
||||||
border-right: 1px solid #b9b9b9
|
border-right: 1px solid #b9b9b9
|
||||||
|
@ -190,7 +192,7 @@
|
||||||
.oe_kanban_title
|
.oe_kanban_title
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
margin: 2px 4px
|
margin: 2px 4px
|
||||||
.oe_kanban_grouped .oe_kanban_record
|
&.oe_kanban_grouped .oe_kanban_record
|
||||||
margin-bottom: 6px
|
margin-bottom: 6px
|
||||||
.oe_kanban_gravatar
|
.oe_kanban_gravatar
|
||||||
display: block
|
display: block
|
||||||
|
@ -225,9 +227,9 @@
|
||||||
.oe_kanban_show_more
|
.oe_kanban_show_more
|
||||||
clear: both
|
clear: both
|
||||||
text-align: center
|
text-align: center
|
||||||
.oe_kanban_grouped .oe_kanban_show_more .oe_button
|
&.oe_kanban_grouped .oe_kanban_show_more .oe_button
|
||||||
width: 100%
|
width: 100%
|
||||||
.oe_kanban_ungrouped
|
&.oe_kanban_ungrouped .oe_kanban_column
|
||||||
background: white
|
background: white
|
||||||
.oe_kanban_record
|
.oe_kanban_record
|
||||||
float: left
|
float: left
|
||||||
|
|
|
@ -174,6 +174,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
||||||
},
|
},
|
||||||
do_process_groups: function(groups) {
|
do_process_groups: function(groups) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
this.$element.remove('oe_kanban_ungrouped').addClass('oe_kanban_grouped');
|
||||||
this.add_group_mutex.exec(function() {
|
this.add_group_mutex.exec(function() {
|
||||||
self.do_clear_groups();
|
self.do_clear_groups();
|
||||||
self.dataset.ids = [];
|
self.dataset.ids = [];
|
||||||
|
@ -195,6 +196,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
||||||
},
|
},
|
||||||
do_process_dataset: function(dataset) {
|
do_process_dataset: function(dataset) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
this.$element.remove('oe_kanban_grouped').addClass('oe_kanban_ungrouped');
|
||||||
this.add_group_mutex.exec(function() {
|
this.add_group_mutex.exec(function() {
|
||||||
var def = $.Deferred();
|
var def = $.Deferred();
|
||||||
self.do_clear_groups();
|
self.do_clear_groups();
|
||||||
|
@ -296,10 +298,10 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
|
||||||
if (!group.state.folded) {
|
if (!group.state.folded) {
|
||||||
if (182*unfolded>=self.$element.width()) {
|
if (182*unfolded>=self.$element.width()) {
|
||||||
group.$element.css('width', "170px");
|
group.$element.css('width', "170px");
|
||||||
} else if (262*unfolded>self.$element.width()) {
|
} else if (262*unfolded<self.$element.width()) {
|
||||||
group.$element.css('width', Math.round(100/unfolded) + '%');
|
|
||||||
} else {
|
|
||||||
group.$element.css('width', "250px");
|
group.$element.css('width', "250px");
|
||||||
|
} else {
|
||||||
|
group.$element.css('width', Math.floor(self.$element.width()/unfolded) + 'px');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -721,9 +723,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) {
|
this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).then(function(records) {
|
||||||
if (records.length) {
|
if (records.length) {
|
||||||
self.set_record(records[0]);
|
self.set_record(records[0]);
|
||||||
var $render = $(self.render());
|
this.replaceElement($(self.render()));
|
||||||
self.$element.replaceWith($render);
|
|
||||||
self.$element = $render;
|
|
||||||
self.$element.data('widget', self);
|
self.$element.data('widget', self);
|
||||||
self.bind_events();
|
self.bind_events();
|
||||||
self.group.compute_cards_auto_height();
|
self.group.compute_cards_auto_height();
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
</td>
|
</td>
|
||||||
</t>
|
</t>
|
||||||
<t t-name="KanbanView.group_records_container">
|
<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_group_list_header"/>
|
||||||
<div class="oe_kanban_show_more">
|
<div class="oe_kanban_show_more">
|
||||||
<button class="oe_button">Show more... (<span class="oe_kanban_remaining"></span> remaining)</button>
|
<button class="oe_button">Show more... (<span class="oe_kanban_remaining"></span> remaining)</button>
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
{
|
{
|
||||||
"name" : "OpenERP Web Mobile",
|
"name" : "OpenERP Web Mobile",
|
||||||
"category": "Hidden",
|
"category": "Hidden",
|
||||||
"description":
|
"description":"""OpenERP Web Mobile.""",
|
||||||
"""
|
|
||||||
OpenERP Web Mobile.
|
|
||||||
""",
|
|
||||||
"version" : "2.0",
|
"version" : "2.0",
|
||||||
"depends" : [],
|
"depends" : [],
|
||||||
'auto_install': True,
|
'auto_install': True,
|
||||||
|
|
|
@ -48,8 +48,7 @@ instance.web_mobile.Login = instance.web.OldWidget.extend({
|
||||||
jQuery("#oe_header").children().remove();
|
jQuery("#oe_header").children().remove();
|
||||||
this.rpc("/web/database/get_list", {}, function(result) {
|
this.rpc("/web/database/get_list", {}, function(result) {
|
||||||
self.db_list = result.db_list;
|
self.db_list = result.db_list;
|
||||||
$('#'+self.element_id).html(self.render(self));
|
this.setElement($('#'+self.element_id).html(self.render(self)));
|
||||||
self.$element = $('#'+self.element_id);
|
|
||||||
if(self.session.db!=""){
|
if(self.session.db!=""){
|
||||||
self.$element.find("#database").val(self.session.db);
|
self.$element.find("#database").val(self.session.db);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
{
|
{
|
||||||
"name" : "Process",
|
"name" : "Process",
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"description":
|
"description":"""OpenERP Web process view.""",
|
||||||
"""
|
|
||||||
OpenERP Web process view.
|
|
||||||
""",
|
|
||||||
"depends" : ["web_diagram"],
|
"depends" : ["web_diagram"],
|
||||||
"js": [
|
"js": [
|
||||||
'static/lib/dracula/*.js',
|
'static/lib/dracula/*.js',
|
||||||
|
|
|
@ -3,12 +3,13 @@ openerp.web_process = function (instance) {
|
||||||
_t = instance.web._t;
|
_t = instance.web._t;
|
||||||
instance.web.ViewManager.include({
|
instance.web.ViewManager.include({
|
||||||
start: function() {
|
start: function() {
|
||||||
this._super();
|
var _super = this._super();
|
||||||
this.process_check();
|
this.process_check();
|
||||||
this.process_help = this.action ? this.action.help : 'Help: Not Defined';
|
this.process_help = this.action ? this.action.help : 'Help: Not Defined';
|
||||||
this.model = this.dataset.model;
|
this.model = this.dataset.model;
|
||||||
if(this.action) this.process_model = this.action.res_model;
|
if(this.action) this.process_model = this.action.res_model;
|
||||||
else this.process_model = this.model;
|
else this.process_model = this.model;
|
||||||
|
return _super;
|
||||||
},
|
},
|
||||||
process_check: function() {
|
process_check: function() {
|
||||||
var self = this,
|
var self = this,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name" : "OpenERP Web Web",
|
"name" : "OpenERP Web Web",
|
||||||
"category" : "Hidden",
|
"category" : "Hidden",
|
||||||
"description":'Openerp Web Web',
|
"description":"""Openerp Web Web.""",
|
||||||
"version" : "2.0",
|
"version" : "2.0",
|
||||||
"depends" : [],
|
"depends" : [],
|
||||||
"installable" : False,
|
"installable" : False,
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Tests",
|
"name": "Tests",
|
||||||
"category": "Hidden",
|
"category": "Hidden",
|
||||||
"description":
|
"description":"""OpenERP Web test suite.""",
|
||||||
"""
|
|
||||||
OpenERP Web test suite.
|
|
||||||
""",
|
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"depends": [],
|
"depends": [],
|
||||||
"js": ["static/src/js/*.js"],
|
"js": ["static/src/js/*.js"],
|
||||||
|
|
109
doc/addons.rst
109
doc/addons.rst
|
@ -113,103 +113,6 @@ initializing the addon.
|
||||||
Creating new standard roles
|
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
|
Views
|
||||||
+++++
|
+++++
|
||||||
|
|
||||||
|
@ -541,18 +444,6 @@ Python
|
||||||
.. _promise object:
|
.. _promise object:
|
||||||
http://api.jquery.com/deferred.promise/
|
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:
|
.. _Rosetta:
|
||||||
.. _Launchpad's own translation tool:
|
.. _Launchpad's own translation tool:
|
||||||
https://help.launchpad.net/Translations
|
https://help.launchpad.net/Translations
|
||||||
|
|
|
@ -16,6 +16,7 @@ Contents:
|
||||||
async
|
async
|
||||||
rpc
|
rpc
|
||||||
|
|
||||||
|
widget
|
||||||
search-view
|
search-view
|
||||||
|
|
||||||
list-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