merged with trunk

bzr revid: dhr@tinyerp.com-20120725045258-xp2ujf7b4dn8v2ms
This commit is contained in:
Dharti Ratani (OpenERP)
2012-07-25 10:22:58 +05:30
15 changed files with 247 additions and 120 deletions
+1
View File
@@ -20,6 +20,7 @@
"static/lib/jquery.form/jquery.form.js",
"static/lib/jquery.validate/jquery.validate.js",
"static/lib/jquery.ba-bbq/jquery.ba-bbq.js",
"static/lib/spinjs/spin.js",
"static/lib/jquery.blockUI/jquery.blockUI.js",
"static/lib/jquery.ui/js/jquery-ui-1.8.17.custom.min.js",
"static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js",
+25 -30
View File
@@ -1,4 +1,4 @@
@charset "UTF-8";
@charset "utf-8";
@font-face {
font-family: "mnmliconsRegular";
src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot");
@@ -1408,20 +1408,31 @@
filter: alpha(opacity=50);
opacity: 0.5;
}
.openerp .oe_searchview .oe_searchview_search {
font-size: 1px;
letter-spacing: -1px;
color: transparent;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
position: absolute;
left: 3px;
top: 1px;
padding: 0;
border: none;
background: transparent;
}
.openerp .oe_searchview .oe_searchview_search:before {
font: 21px "mnmliconsRegular";
content: "r";
color: #a3a3a3;
}
.openerp .oe_searchview .oe_searchview_facets {
min-height: 22px;
}
.openerp .oe_searchview .oe_searchview_facets:before {
color: #cccccc;
font-family: "mnmliconsRegular";
content: "r";
font-size: 130%;
display: inline;
position: relative;
left: 6px;
top: 2px;
color: #a3a3a3;
padding-right: 4px;
margin-left: 15px;
}
.openerp .oe_searchview .oe_searchview_facets * {
vertical-align: top;
@@ -2180,7 +2191,7 @@
height: auto;
line-height: 16px;
}
.openerp .oe_form_field_one2many .oe_list_buttons.oe_editing .oe_list_save {
.openerp .oe_form_field_one2many .oe_list_buttons.oe_editing .oe_list_save, .openerp .oe_form_field_many2many .oe_list_buttons.oe_editing .oe_list_save {
visibility: hidden;
}
.openerp .oe_form .oe_form_field_many2many > .oe_list .oe_list_pager_single_page {
@@ -2266,9 +2277,6 @@
padding: 3px 6px;
white-space: pre-line;
}
.openerp .oe_list_content > tbody > tr > td.oe_list_field_cell progress {
width: 100%;
}
.openerp .oe_list_content > tbody > tr > td > button, .openerp .oe_list_content > tbody > tr > th > button {
border: none;
background: transparent;
@@ -2441,19 +2449,6 @@
.kitten-mode-activated > * {
opacity: 0.7;
}
.kitten-mode-activated .oe_footer a {
background-image: url(http://www.risacher.com/la-rache/zfiles/la-rache.png);
font-size: 1px;
letter-spacing: -1px;
color: transparent;
display: inline-block;
height: 15px;
width: 80px;
vertical-align: top;
}
.kitten-mode-activated .oe_footer a span {
display: none;
}
div.ui-widget-overlay {
background: black;
+12 -11
View File
@@ -1101,19 +1101,20 @@ $sheet-max-width: 860px
border-right: 5px solid transparent
@include opacity()
.oe_searchview_search
@include text-to-icon("r", #a3a3a3)
@include box-shadow(none)
@include radius(0)
position: absolute
left: 3px
top: 1px
padding: 0
border: none
background: transparent
.oe_searchview_facets
min-height: 22px
&:before
color: #ccc
font-family: "mnmliconsRegular"
content: "r"
font-size: 130%
display: inline
position: relative
left: 6px
top: 2px
color: #a3a3a3
padding-right: 4px
margin-left: 15px
*
vertical-align: top
display: inline-block
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

+7 -7
View File
@@ -257,7 +257,7 @@ instance.web.Loading = instance.web.Widget.extend({
// Block UI after 3s
this.long_running_timer = setTimeout(function () {
self.blocked_ui = true;
$.blockUI();
instance.web.blockUI();
}, 3000);
}
@@ -276,7 +276,7 @@ instance.web.Loading = instance.web.Widget.extend({
// Don't unblock if blocked by somebody else
if (self.blocked_ui) {
this.blocked_ui = false;
$.unblockUI();
instance.web.unblockUI();
}
this.$element.fadeOut();
this.getParent().$element.removeClass('oe_wait');
@@ -287,7 +287,7 @@ instance.web.Loading = instance.web.Widget.extend({
instance.web.DatabaseManager = instance.web.Widget.extend({
init: function(parent) {
this._super(parent);
this.unblockUIFunction = $.unblockUI;
this.unblockUIFunction = instance.web.unblockUI;
$.validator.addMethod('matches', function (s, _, re) {
return new RegExp(re).test(s);
}, _t("Invalid database name"));
@@ -354,16 +354,16 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
* from unblocking the UI
*/
blockUI: function () {
$.blockUI();
$.unblockUI = function () {};
instance.web.blockUI();
instance.web.unblockUI = function () {};
},
/**
* Reinstates $.unblockUI so third parties can play with blockUI, and
* unblocks the UI
*/
unblockUI: function () {
$.unblockUI = this.unblockUIFunction;
$.unblockUI();
instance.web.unblockUI = this.unblockUIFunction;
instance.web.unblockUI();
},
/**
* Displays an error dialog resulting from the various RPC communications
+33 -1
View File
@@ -550,7 +550,39 @@ $.async_when = function() {
/** Setup blockui */
if ($.blockUI) {
$.blockUI.defaults.baseZ = 1100;
$.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">';
$.blockUI.defaults.message = '<div class="oe_blockui_spin" style="height: 50px">';
$.blockUI.defaults.css.border = '0';
$.blockUI.defaults.css["background-color"] = '';
$.blockUI.spinners = [];
}
instance.web.blockUI = function() {
var tmp = $.blockUI.apply($, arguments);
var target = $(".oe_blockui_spin")[0];
var opts = {
lines: 13, // The number of lines to draw
length: 7, // The length of each line
width: 4, // The line thickness
radius: 10, // The radius of the inner circle
rotate: 0, // The rotation offset
color: '#FFF', // #rgb or #rrggbb
speed: 1, // Rounds per second
trail: 60, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
className: 'spinner', // The CSS class to assign to the spinner
zIndex: 2e9, // The z-index (defaults to 2000000000)
top: 'auto', // Top position relative to parent in px
left: 'auto' // Left position relative to parent in px
};
var spinner = new Spinner(opts).spin(target);
$.blockUI.spinners.push(spinner);
return tmp;
}
instance.web.unblockUI = function() {
_.each($.blockUI.spinners, function(el) {
el.stop();
});
return $.unblockUI.apply($, arguments);
}
/** Setup default session */
+2 -2
View File
@@ -376,7 +376,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
exported_fields.unshift({name: 'id', label: 'External ID'});
var export_format = this.$element.find("#export_format").val();
$.blockUI();
instance.web.blockUI();
this.session.get_file({
url: '/web/export/' + export_format,
data: {data: JSON.stringify({
@@ -387,7 +387,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
import_compat: Boolean(
this.$element.find("#import_compat").val())
})},
complete: $.unblockUI
complete: instance.web.unblockUI
});
},
close: function() {
+17 -1
View File
@@ -330,6 +330,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
});
}
// Launch a search on clicking the oe_searchview_search button
this.$element.on('click', 'button.oe_searchview_search', function (e) {
e.stopImmediatePropagation();
self.do_search();
});
this.$element.on('keydown',
'.oe_searchview_input, .oe_searchview_facet', function (e) {
switch(e.which) {
@@ -1475,7 +1481,17 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
facet_for: function (value) {
var self = this;
if (value instanceof Array) {
return $.when(facet_from(this, value));
if (value.length === 2 && _.isString(value[1])) {
return $.when(facet_from(this, value));
}
if (value.length > 1) {
// more than one search_default m2o id? Should we OR them?
throw new Error(
_("M2O search fields do not currently handle multiple default values"));
}
// there are many cases of {search_default_$m2ofield: [id]}, need
// to handle this as if it were a single value.
value = value[0];
}
return this.model.call('name_get', [value], {}).pipe(function (names) {
if (_(names).isEmpty()) { return null; }
+11 -6
View File
@@ -3027,8 +3027,11 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
controller.o2m = self;
if (view_type == "list") {
if (self.get("effective_readonly"))
controller.set_editable(false);
if (self.get("effective_readonly")) {
controller.on('edit:before', self, function (e) {
e.cancel = true;
});
}
} else if (view_type === "form") {
if (self.get("effective_readonly")) {
$(".oe_form_buttons", controller.$element).children().remove();
@@ -3301,7 +3304,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
.value();
},
do_add_record: function () {
if (this.options.editable) {
if (this.editable()) {
this._super.apply(this, arguments);
} else {
var self = this;
@@ -4108,10 +4111,12 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
self.dataset, false,
_.extend({'deletable': false,
'selectable': !self.options.disable_multiple_selection,
'read_only': true,
'import_enabled': false,
'$buttons': self.$buttonpane,
}, self.options.list_view_options || {}));
self.view_list.on('edit:before', self, function (e) {
e.cancel = true;
});
self.view_list.popup = self;
self.view_list.appendTo($(".oe_popup_list", self.$element)).pipe(function() {
self.view_list.do_show();
@@ -4343,7 +4348,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
//link.target = '_blank';
link.href = "data:application/octet-stream;base64," + value;
} else {
$.blockUI();
instance.web.blockUI();
this.session.get_file({
url: '/web/binary/saveas_ajax',
data: {data: JSON.stringify({
@@ -4353,7 +4358,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
filename_field: (this.node.attrs.filename || ''),
context: this.view.dataset.get_context()
})},
complete: $.unblockUI,
complete: instance.web.unblockUI,
error: instance.webclient.crashmanager.on_rpc_error
});
ev.stopPropagation();
-3
View File
@@ -21,9 +21,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
// whether the view rows can be reordered (via vertical drag & drop)
'reorderable': true,
'action_buttons': true,
// if true, the view can't be editable, ignoring the view's and the context's
// instructions
'read_only': false,
// if true, the 'Import', 'Export', etc... buttons will be shown
'import_enabled': true,
},
+97 -36
View File
@@ -12,6 +12,8 @@ openerp.web.list_editable = function (instance) {
var self = this;
this._super.apply(this, arguments);
this._force_editability = null;
this._context_editable = false;
this.editor = this.make_editor();
// Stores records of {field, cell}, allows for re-rendering fields
// depending on cell state during and after resize events
@@ -37,6 +39,11 @@ openerp.web.list_editable = function (instance) {
}
});
this.on('edit:before', this, function (event) {
if (!self.editable() || self.editor.is_editing()) {
event.cancel = true;
}
});
this.on('edit:after', this, function () {
self.$element.add(self.$buttons).addClass('oe_editing');
});
@@ -62,26 +69,16 @@ openerp.web.list_editable = function (instance) {
do_edit: function (index, id, dataset) {
_.extend(this.dataset, dataset);
},
/**
* Sets editability status for the list, based on defaults, view
* architecture and the provided flag, if any.
*
* @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom".
*/
set_editable: function (force) {
// TODO: fix handling of editability status to be simpler & clearer & more coherent
// If ``force``, set editability to bottom
// otherwise rely on view default
// view' @editable is handled separately as we have not yet
// fetched and processed the view at this point.
this.options.editable = (
! this.options.read_only && ((force && "bottom") || this.defaults.editable));
editable: function () {
return this.fields_view.arch.attrs.editable
|| this._context_editable
|| this.options.editable;
},
/**
* Replace do_search to handle editability process
*/
do_search: function(domain, context, group_by) {
this.set_editable(context['set_editable']);
this._context_editable = !!context.set_editable;
this._super.apply(this, arguments);
},
/**
@@ -89,7 +86,7 @@ openerp.web.list_editable = function (instance) {
* as an editable row at the top or bottom of the list)
*/
do_add_record: function () {
if (this.options.editable) {
if (this.editable()) {
this.$element.find('table:first').show();
this.$element.find('.oe_view_nocontent').remove();
this.start_edition();
@@ -103,9 +100,8 @@ openerp.web.list_editable = function (instance) {
this.editor.destroy();
}
// tree/@editable takes priority on everything else if present.
this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable);
var result = this._super(data, grouped);
if (this.options.editable) {
if (this.editable()) {
// FIXME: any hook available to ensure this is only done once?
this.$buttons
.off('click', '.oe_list_save')
@@ -202,12 +198,20 @@ openerp.web.list_editable = function (instance) {
return;
}
// FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow
field.$element.attr('data-fieldname', field_name);
self.fields_for_resize.push({field: field, cell: cell});
}, options).pipe(function () {
$recordRow.addClass('oe_edition');
self.resize_fields();
return record.attributes;
});
}).fail(function () {
// if the start_edition event is cancelled and it was a
// creation, remove the newly-created empty record
if (!record.get('id')) {
self.records.remove(record);
}
});
});
},
@@ -377,7 +381,7 @@ openerp.web.list_editable = function (instance) {
return this.reload_record(record);
},
prepends_on_create: function () {
return this.options.editable === 'top';
return this.editable() === 'top';
},
setup_events: function () {
var self = this;
@@ -399,18 +403,20 @@ openerp.web.list_editable = function (instance) {
*
* @private
* @param {String} [next_record='succ'] method to call on the records collection to get the next record to edit
* @param {Object} [options]
* @param {String} [options.focus_field]
* @return {*}
*/
_next: function (next_record) {
_next: function (next_record, options) {
next_record = next_record || 'succ';
var self = this;
return this.save_edition().pipe(function (saveInfo) {
if (saveInfo.created) {
return self.start_edition();
}
return self.start_edition(
self.records[next_record](
saveInfo.record, {wraparound: true}));
var record = self.records[next_record](
saveInfo.record, {wraparound: true});
return self.start_edition(record, options);
});
},
keyup_ENTER: function () {
@@ -440,25 +446,80 @@ openerp.web.list_editable = function (instance) {
}
return selection.start;
},
keydown_UP: function (e) {
/**
* @param DOMEvent event
* @param {String} record_direction direction to move into to get the next record (pred | succ)
* @param {Function} is_valid_move whether the edition should be moved to the next record
* @private
*/
_key_move_record: function (event, record_direction, is_valid_move) {
if (!this.editor.is_editing('edit')) { return $.when(); }
// FIXME: assumes editable widgets are input-type elements
var index = this._text_cursor(e.target);
var index = this._text_cursor(event.target);
// If selecting or not at the start of the input
if (index === null || index !== 0) { return $.when(); }
if (!is_valid_move(event.target, index)) { return $.when(); }
e.preventDefault();
return this._next('pred');
event.preventDefault();
var source_field = $(event.target).closest('[data-fieldname]')
.attr('data-fieldname');
return this._next(record_direction, {focus_field: source_field});
},
keydown_UP: function (e) {
return this._key_move_record(e, 'pred', function (el, index) {
return index === 0;
});
},
keydown_DOWN: function (e) {
if (!this.editor.is_editing('edit')) { return $.when(); }
// FIXME: assumes editable widgets are input-type elements
var index = this._text_cursor(e.target);
// If selecting or not at the end of the input
if (index === null || index !== e.target.value.length) { return $.when(); }
return this._key_move_record(e, 'succ', function (el, index) {
return index === el.value.length;
});
},
e.preventDefault();
return this._next();
keydown_LEFT: function (e) {
// If the cursor is at the beginning of the field
var source_field = $(e.target).closest('[data-fieldname]')
.attr('data-fieldname');
var index = this._text_cursor(e.target);
if (index !== 0) { return $.when(); }
var fields_order = this.editor.form.fields_order;
var field_index = _(fields_order).indexOf(source_field);
// Look for the closest visible form field to the left
var fields = this.editor.form.fields;
var field;
do {
if (--field_index < 0) { return $.when(); }
field = fields[fields_order[field_index]];
} while (!field.$element.is(':visible'));
// and focus it
field.focus();
return $.when();
},
keydown_RIGHT: function (e) {
// same as above, but with cursor at the end of the field and
// looking for new fields at the right
var source_field = $(e.target).closest('[data-fieldname]')
.attr('data-fieldname');
var index = this._text_cursor(e.target);
if (index !== e.target.value.length) { return $.when(); }
var fields_order = this.editor.form.fields_order;
var field_index = _(fields_order).indexOf(source_field);
var fields = this.editor.form.fields;
var field;
do {
if (++field_index >= fields_order.length) { return $.when(); }
field = fields[fields_order[field_index]];
} while (!field.$element.is(':visible'));
field.focus();
return $.when();
},
keydown_TAB: function (e) {
var form = this.editor.form;
@@ -642,7 +703,7 @@ openerp.web.list_editable = function (instance) {
instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{
row_clicked: function (event) {
if (!this.options.editable) {
if (!this.view.editable()) {
return this._super.apply(this, arguments);
}
var record_id = $(event.currentTarget).data('id');
+4 -4
View File
@@ -274,7 +274,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
},
ir_actions_report_xml: function(action, on_closed) {
var self = this;
$.blockUI();
instance.web.blockUI();
self.rpc("/web/session/eval_domain_and_context", {
contexts: [action.context],
domains: []
@@ -284,7 +284,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
self.session.get_file({
url: '/web/report',
data: {action: JSON.stringify(action)},
complete: $.unblockUI,
complete: instance.web.unblockUI,
success: function(){
if (!self.dialog && on_closed) {
on_closed();
@@ -841,7 +841,7 @@ instance.web.Sidebar = instance.web.Widget.extend({
} else {
self.do_attachement_update(self.dataset, self.model_id);
}
$.unblockUI();
instance.web.unblockUI();
});
},
start: function() {
@@ -980,7 +980,7 @@ instance.web.Sidebar = instance.web.Widget.extend({
$e.parent().find('input[type=file]').prop('disabled', true);
$e.parent().find('button').prop('disabled', true).find('img, span').toggle();
this.$('.oe_sidebar_add_attachment span').text(_t('Uploading...'));
$.blockUI();
instance.web.blockUI();
}
},
on_attachment_delete: function(e) {
+2
View File
@@ -1220,6 +1220,8 @@
<div class="oe_searchview_clear"/>
<div class="oe_searchview_unfold_drawer" title="Advanced Search..."/>
<div class="oe_searchview_drawer"/>
<button type="button" class="oe_searchview_search"
title="Search Again">Search</button>
</div>
<div t-name="SearchView.InputView"
+6
View File
@@ -47,3 +47,9 @@ Undocumented stuff
* What is the difference between ``readonly`` and
``effective_readonly``?
* No facilities for DOM events handling/delegations e.g. handling
keyup/keydown/keypress from a form fields into the form's user.
* Also no way to reverse from a DOM node (e.g. DOMEvent#target) back to a
form view field easily
+30 -19
View File
@@ -87,34 +87,37 @@ List view edition is an extension to the base listview providing the
capability of inline record edition by delegating to an embedded form
view.
.. todo::
Editability status
++++++++++++++++++
cleanup options and settings for editability configuration. Right
now there are:
The editability status of a list view can be queried through the
:js:func:`~openerp.web.ListView.editable` method, will return a falsy
value if the listview is not currently editable.
``defaults.editable``
The editability status is based on three flags:
``null``, ``"top"`` or ``"bottom"``, generally broken and
useless
``tree/@editable``
``context.set_editable``
If present, can be either ``"top"`` or ``"bottom"``. Either will
make the list view editable, with new records being respectively
created at the top or at the bottom of the view.
forces ``options.editable`` to ``"bottom"``
``context.set_editable``
``view.arch.attrs.editable``
Boolean flag extracted from a search context (during the
:js:func:`~openerp.web.ListView.do_search`` handler), ``true``
will make the view editable (from the top), ``false`` or the
absence of the flag is a noop.
same as ``defaults.editable``, but applied separately (after
reloading the view), if absent delegates to
``options.editable`` which may have been set previously.
``defaults.editable``
``options.read_only``
Like ``tree/@editable``, one of absent (``null``)), ``"top"`` or
``"bottom"``, fallback for the list view if none of the previous
two flags are set.
force options.editable to false, or something?
.. note:: can probably be replaced by cancelling ``edit:before``
and :js:func:`~openerp.web.ListView.set_editable` which
ultimately behaves weird-as-fuck-ly.
These three flags can only *make* a listview editable, they can *not*
override a previously set flag. To do that, a listview user should
instead cancel :ref:`the edit:before event <listview-edit-before>`.
The editable list view module adds a number of methods to the list
view, on top of implementing the :js:class:`EditorDelegate` protocol:
@@ -219,6 +222,14 @@ view provides a number of dedicated events to its lifecycle.
abort its current behavior as soon as possible, and rollback
any state modification.
Generally speaking, an event should only be cancelled (by
setting the ``cancel`` flag to ``true``), uncancelling an
event is undefined as event handlers are executed on a
first-come-first-serve basis and later handlers may
re-cancel an uncancelled event.
.. _listview-edit-before:
``edit:before`` *cancellable*
Invoked before the list view starts editing a record.