[REF] *: use 'frontend' system in non-website apps

* account, auth_signup, payment, portal, project, sale, sale_management,
  web_unsplash, website, website_mail, website_rating, website_sale

This commit does probably not do what is stated for all non-website apps
but it is a first step. It also uses the system in apps which could have
already used it but did not.

Part of https://github.com/odoo/odoo/pull/29442
task-1932066
This commit is contained in:
qsm-odoo
2019-02-26 17:09:22 +00:00
parent c3da83b40a
commit 27fca749af
17 changed files with 1335 additions and 1178 deletions
@@ -1,40 +1,25 @@
odoo.define('account.AccountPortalSidebar.instance', function (require) {
"use strict";
require('web.dom_ready');
var AccountPortalSidebar = require('account.AccountPortalSidebar');
if (!$('.o_portal_invoice_sidebar').length) {
return $.Deferred().reject("DOM doesn't contain '.o_portal_invoice_sidebar'");
}
var account_portal_sidebar = new AccountPortalSidebar();
return account_portal_sidebar.attachTo($('.o_portal_invoice_sidebar')).then(function () {
return account_portal_sidebar;
});
});
//==============================================================================
odoo.define('account.AccountPortalSidebar', function (require) {
"use strict";
'use strict';
var publicWidget = require('web.public.widget');
var PortalSidebar = require('portal.PortalSidebar');
var AccountPortalSidebar = PortalSidebar.extend({
publicWidget.registry.AccountPortalSidebar = PortalSidebar.extend({
selector: '.o_portal_invoice_sidebar',
events: {
'click .o_portal_invoice_print': '_onPrintInvoice',
},
/**
* @override
*/
start: function () {
var self = this;
this._super.apply(this, arguments);
var def = this._super.apply(this, arguments);
var $invoiceHtml = this.$el.find('iframe#invoice_html');
var updateIframeSize = self._updateIframeSize.bind(self, $invoiceHtml);
var updateIframeSize = this._updateIframeSize.bind(this, $invoiceHtml);
$invoiceHtml.on('load', updateIframeSize);
$(window).on('resize', updateIframeSize);
return def;
},
//--------------------------------------------------------------------------
@@ -64,7 +49,4 @@ var AccountPortalSidebar = PortalSidebar.extend({
this._printIframeContent(href);
},
});
return AccountPortalSidebar;
});
+19 -10
View File
@@ -1,16 +1,25 @@
odoo.define('auth_signup.signup', function (require) {
'use strict';
'use strict';
require('web.dom_ready');
var publicWidget = require('web.public.widget');
// Disable 'Sign Up' button to prevent user form continuous clicking
if ($('.oe_signup_form').length > 0) {
$('.oe_signup_form').on('submit', function (ev) {
var $form = $(ev.currentTarget);
var $btn = $form.find('.oe_login_buttons > button[type="submit"]');
publicWidget.registry.SignUpForm = publicWidget.Widget.extend({
selector: '.oe_signup_form',
events: {
'submit': '_onSubmit',
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onSubmit: function () {
var $btn = this.$('.oe_login_buttons > button[type="submit"]');
$btn.attr('disabled', 'disabled');
$btn.prepend('<i class="fa fa-refresh fa-spin"/> ');
});
}
},
});
});
+430 -392
View File
@@ -1,234 +1,163 @@
odoo.define('payment.payment_form', function (require) {
"use strict";
"use strict";
var ajax = require('web.ajax');
var config = require('web.config');
var core = require('web.core');
var dom = require('web.dom');
var Dialog = require("web.Dialog");
var Widget = require("web.Widget");
var rpc = require("web.rpc");
var _t = core._t;
var core = require('web.core');
var Dialog = require('web.Dialog');
var publicWidget = require('web.public.widget');
var PaymentForm = Widget.extend({
var _t = core._t;
events: {
'click #o_payment_form_pay': 'payEvent',
'click #o_payment_form_add_pm': 'addPmEvent',
'click button[name="delete_pm"]': 'deletePmEvent',
'click .o_payment_acquirer_select': 'radioClickEvent',
'click .o_payment_form_pay_icon_more': 'onClickMorePaymentIcon',
},
publicWidget.registry.PaymentForm = publicWidget.Widget.extend({
selector: '.o_payment_form',
events: {
'click #o_payment_form_pay': 'async payEvent',
'click #o_payment_form_add_pm': 'addPmEvent',
'click button[name="delete_pm"]': 'deletePmEvent',
'click .o_payment_form_pay_icon_more': 'onClickMorePaymentIcon',
'click .o_payment_acquirer_select': 'radioClickEvent',
},
init: function(parent, options) {
this._super.apply(this, arguments);
this.options = _.extend(options || {}, {
});
/**
* @override
*/
start: function () {
this.options = _.extend(this.$el.data(), this.options);
this.updateNewPaymentDisplayStatus();
$('[data-toggle="tooltip"]').tooltip();
return this._super.apply(this, arguments);
},
// TODO simplify this using the 'async' keyword in the events
// property definition as soon as this widget is converted in
// frontend widget.
this.payEvent = dom.makeButtonHandler(this.payEvent);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
start: function () {
this.updateNewPaymentDisplayStatus();
$('[data-toggle="tooltip"]').tooltip();
},
/**
* @private
* @param {string} title
* @param {string} message
*/
displayError: function (title, message) {
var $checkedRadio = this.$('input[type="radio"]:checked'),
acquirerID = this.getAcquirerIdFromRadio($checkedRadio[0]);
var $acquirerForm;
if (this.isNewPaymentRadio($checkedRadio[0])) {
$acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID);
}
else if (this.isFormPaymentRadio($checkedRadio[0])) {
$acquirerForm = this.$('#o_payment_form_acq_' + acquirerID);
}
payEvent: function (ev) {
ev.preventDefault();
var form = this.el;
var checked_radio = this.$('input[type="radio"]:checked');
var self = this;
var button = ev.target;
// first we check that the user has selected a payment method
if (checked_radio.length === 1) {
checked_radio = checked_radio[0];
// we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array
var acquirer_id = this.getAcquirerIdFromRadio(checked_radio);
var acquirer_form = false;
if (this.isNewPaymentRadio(checked_radio)) {
acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id);
} else {
acquirer_form = this.$('#o_payment_form_acq_' + acquirer_id);
}
var inputs_form = $('input', acquirer_form);
var ds = $('input[name="data_set"]', acquirer_form)[0];
// if the user is adding a new payment
if (this.isNewPaymentRadio(checked_radio)) {
if (this.options.partnerId === undefined) {
console.warn('payment_form: unset partner_id when adding new token; things could go wrong');
}
var form_data = this.getFormData(inputs_form);
var wrong_input = false;
inputs_form.toArray().forEach(function (element) {
//skip the check of non visible inputs
if ($(element).attr('type') == 'hidden') {
return true;
}
$(element).closest('div.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
$(element).siblings( ".o_invalid_field" ).remove();
//force check of forms validity (useful for Firefox that refill forms automatically on f5)
$(element).trigger("focusout");
if (element.dataset.isRequired && element.value.length === 0) {
$(element).closest('div.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
$(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>');
wrong_input = true;
}
else if ($(element).closest('div.form-group').hasClass('o_has_error')) {
wrong_input = true;
$(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>');
}
});
if (wrong_input) {
return;
}
$(button).attr('disabled', true);
$(button).children('.fa-plus-circle').removeClass('fa-plus-circle')
$(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i>&nbsp;</span>');
var verify_validity = this.$el.find('input[name="verify_validity"]');
if (verify_validity.length>0) {
form_data.verify_validity = verify_validity[0].value === "1";
}
// do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route'
return ajax.jsonRpc(ds.dataset.createRoute, 'call', form_data).then(function (data) {
// if the server has returned true
if (data.result) {
// and it need a 3DS authentication
if (data['3d_secure'] !== false) {
// then we display the 3DS page to the user
$("body").html(data['3d_secure']);
}
else {
checked_radio.value = data.id; // set the radio value to the new card id
form.submit();
return $.Deferred();
}
}
// if the server has returned false, we display an error
else {
if (data.error) {
self.displayError(
'',
data.error);
} else { // if the server doesn't provide an error message
self.displayError(
_t('Server Error'),
_t('e.g. Your credit card details are wrong. Please verify.'));
}
}
// here we remove the 'processing' icon from the 'add a new payment' button
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle')
$(button).find('span.o_loader').remove();
}).fail(function (error, event) {
// if the rpc fails, pretty obvious
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle')
$(button).find('span.o_loader').remove();
self.displayError(
_t('Server Error'),
_t("We are not able to add your payment method at the moment.") +
error.data.message
);
});
}
// if the user is going to pay with a form payment, then
else if (this.isFormPaymentRadio(checked_radio)) {
var $tx_url = this.$el.find('input[name="prepare_tx_url"]');
// if there's a prepare tx url set
if ($tx_url.length === 1) {
// if the user wants to save his credit card info
var form_save_token = acquirer_form.find('input[name="o_payment_form_save_token"]').prop('checked');
// then we call the route to prepare the transaction
return ajax.jsonRpc($tx_url[0].value, 'call', {
'acquirer_id': parseInt(acquirer_id),
'save_token': form_save_token,
'access_token': self.options.accessToken,
'success_url': self.options.successUrl,
'error_url': self.options.errorUrl,
'callback_method': self.options.callbackMethod,
'order_id': self.options.orderId,
}).then(function (result) {
if (result) {
// if the server sent us the html form, we create a form element
var newForm = document.createElement('form');
newForm.setAttribute("method", "post"); // set it to post
newForm.setAttribute("provider", checked_radio.dataset.provider);
newForm.hidden = true; // hide it
newForm.innerHTML = result; // put the html sent by the server inside the form
var action_url = $(newForm).find('input[name="data_set"]').data('actionUrl');
newForm.setAttribute("action", action_url); // set the action url
$(document.getElementsByTagName('body')[0]).append(newForm); // append the form to the body
$(newForm).find('input[data-remove-me]').remove(); // remove all the input that should be removed
if(action_url) {
newForm.submit(); // and finally submit the form
return $.Deferred();
}
}
else {
self.displayError(
_t('Server Error'),
_t("We are not able to redirect you to the payment form.")
);
}
}).fail(function (error, event) {
self.displayError(
_t('Server Error'),
_t("We are not able to redirect you to the payment form. ") +
error.data.message
);
});
}
else {
// we append the form to the body and send it.
this.displayError(
_t("Cannot set-up the payment"),
_t("We're unable to process your payment.")
);
}
}
else { // if the user is using an old payment then we just submit the form
form.submit();
return $.Deferred();
}
if ($checkedRadio.length === 0) {
return new Dialog(null, {
title: _t('Error: ') + _.str.escapeHTML(title),
size: 'medium',
$content: "<p>" + (_.str.escapeHTML(message) || "") + "</p>" ,
buttons: [
{text: _t('Ok'), close: true}]}).open();
} else {
// removed if exist error message
this.$('#payment_error').remove();
var messageResult = '<div class="alert alert-danger mb4" id="payment_error">';
if (title != '') {
messageResult = messageResult + '<b>' + _.str.escapeHTML(title) + ':</b></br>';
}
else {
this.displayError(
_t('No payment method selected'),
_t('Please select a payment method.')
);
}
},
// event handler when clicking on the button to add a new payment method
addPmEvent: function (ev) {
ev.stopPropagation();
ev.preventDefault();
var checked_radio = this.$('input[type="radio"]:checked');
var self = this;
var button = ev.target;
messageResult = messageResult + _.str.escapeHTML(message) + '</div>';
$acquirerForm.append(messageResult);
}
},
/**
* @private
* @param {DOMElement} element
*/
getAcquirerIdFromRadio: function (element) {
return $(element).data('acquirer-id');
},
/**
* @private
* @param {jQuery} $form
*/
getFormData: function ($form) {
var unindexed_array = $form.serializeArray();
var indexed_array = {};
// we check if the user has selected a 'add a new payment' option
if (checked_radio.length === 1 && this.isNewPaymentRadio(checked_radio[0])) {
// we retrieve which acquirer is used
checked_radio = checked_radio[0];
var acquirer_id = this.getAcquirerIdFromRadio(checked_radio);
var acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id);
// we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array
var inputs_form = $('input', acquirer_form);
$.map(unindexed_array, function (n, i) {
indexed_array[n.name] = n.value;
});
return indexed_array;
},
/**
* @private
* @param {DOMElement} element
*/
isFormPaymentRadio: function (element) {
return $(element).data('form-payment') === 'True';
},
/**
* @private
* @param {DOMElement} element
*/
isNewPaymentRadio: function (element) {
return $(element).data('s2s-payment') === 'True';
},
/**
* @private
*/
updateNewPaymentDisplayStatus: function () {
var checked_radio = this.$('input[type="radio"]:checked');
// we hide all the acquirers form
this.$('[id*="o_payment_add_token_acq_"]').addClass('d-none');
this.$('[id*="o_payment_form_acq_"]').addClass('d-none');
if (checked_radio.length !== 1) {
return;
}
checked_radio = checked_radio[0];
var acquirer_id = this.getAcquirerIdFromRadio(checked_radio);
// if we clicked on an add new payment radio, display its form
if (this.isNewPaymentRadio(checked_radio)) {
this.$('#o_payment_add_token_acq_' + acquirer_id).removeClass('d-none');
}
else if (this.isFormPaymentRadio(checked_radio)) {
this.$('#o_payment_form_acq_' + acquirer_id).removeClass('d-none');
}
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {Event} ev
*/
payEvent: function (ev) {
ev.preventDefault();
var form = this.el;
var checked_radio = this.$('input[type="radio"]:checked');
var self = this;
var button = ev.target;
// first we check that the user has selected a payment method
if (checked_radio.length === 1) {
checked_radio = checked_radio[0];
// we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array
var acquirer_id = this.getAcquirerIdFromRadio(checked_radio);
var acquirer_form = false;
if (this.isNewPaymentRadio(checked_radio)) {
acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id);
} else {
acquirer_form = this.$('#o_payment_form_acq_' + acquirer_id);
}
var inputs_form = $('input', acquirer_form);
var ds = $('input[name="data_set"]', acquirer_form)[0];
// if the user is adding a new payment
if (this.isNewPaymentRadio(checked_radio)) {
if (this.options.partnerId === undefined) {
console.warn('payment_form: unset partner_id when adding new token; things could go wrong');
}
var form_data = this.getFormData(inputs_form);
var ds = $('input[name="data_set"]', acquirer_form)[0];
var wrong_input = false;
inputs_form.toArray().forEach(function (element) {
@@ -242,30 +171,34 @@ odoo.define('payment.payment_form', function (require) {
$(element).trigger("focusout");
if (element.dataset.isRequired && element.value.length === 0) {
$(element).closest('div.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>';
$(element).closest('div.form-group').append(message);
$(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>');
wrong_input = true;
}
else if ($(element).closest('div.form-group').hasClass('o_has_error')) {
wrong_input = true;
var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>';
$(element).closest('div.form-group').append(message);
$(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>');
}
});
if (wrong_input) {
return;
}
// We add a 'processing' icon into the 'add a new payment' button
$(button).attr('disabled', true);
$(button).children('.fa-plus-circle').removeClass('fa-plus-circle')
$(button).children('.fa-plus-circle').removeClass('fa-plus-circle');
$(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i>&nbsp;</span>');
// we force the check when adding a card trough here
form_data.verify_validity = true;
var verify_validity = this.$el.find('input[name="verify_validity"]');
if (verify_validity.length>0) {
form_data.verify_validity = verify_validity[0].value === "1";
}
// do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route'
ajax.jsonRpc(ds.dataset.createRoute, 'call', form_data).then( function (data) {
return this._rpc({
route: ds.dataset.createRoute,
params: form_data,
}).then(function (data) {
// if the server has returned true
if (data.result) {
// and it need a 3DS authentication
@@ -273,15 +206,10 @@ odoo.define('payment.payment_form', function (require) {
// then we display the 3DS page to the user
$("body").html(data['3d_secure']);
}
// if it doesn't require 3DS
else {
// we just go to the return_url or reload the page
if (form_data.return_url) {
window.location = form_data.return_url;
}
else {
window.location.reload();
}
checked_radio.value = data.id; // set the radio value to the new card id
form.submit();
return $.Deferred();
}
}
// if the server has returned false, we display an error
@@ -298,186 +226,296 @@ odoo.define('payment.payment_form', function (require) {
}
// here we remove the 'processing' icon from the 'add a new payment' button
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle')
$(button).children('.fa').addClass('fa-plus-circle');
$(button).find('span.o_loader').remove();
}).fail(function (error, event) {
// if the rpc fails, pretty obvious
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle')
$(button).children('.fa').addClass('fa-plus-circle');
$(button).find('span.o_loader').remove();
self.displayError(
_t('Server error'),
_t("We are not able to add your payment method at the moment.</p>") +
error.data.message
_t('Server Error'),
_t("We are not able to add your payment method at the moment.") +
error.data.message
);
});
}
else {
this.displayError(
_t('No payment method selected'),
_t('Please select the option to add a new payment method.')
);
}
},
// event handler when clicking on a button to delete a payment method
deletePmEvent: function (ev) {
ev.stopPropagation();
ev.preventDefault();
var self = this;
var pm_id = parseInt(ev.target.value);
var tokenDelete = function () {
rpc.query({
model: 'payment.token',
method: 'unlink',
args: [pm_id],
})
.then(function (result) {
if ( result === true) {
ev.target.closest('div').remove();
// if the user is going to pay with a form payment, then
else if (this.isFormPaymentRadio(checked_radio)) {
var $tx_url = this.$el.find('input[name="prepare_tx_url"]');
// if there's a prepare tx url set
if ($tx_url.length === 1) {
// if the user wants to save his credit card info
var form_save_token = acquirer_form.find('input[name="o_payment_form_save_token"]').prop('checked');
// then we call the route to prepare the transaction
return this._rpc({
route: $tx_url[0].value,
params: {
'acquirer_id': parseInt(acquirer_id),
'save_token': form_save_token,
'access_token': self.options.accessToken,
'success_url': self.options.successUrl,
'error_url': self.options.errorUrl,
'callback_method': self.options.callbackMethod,
'order_id': self.options.orderId,
},
}).then(function (result) {
if (result) {
// if the server sent us the html form, we create a form element
var newForm = document.createElement('form');
newForm.setAttribute("method", "post"); // set it to post
newForm.setAttribute("provider", checked_radio.dataset.provider);
newForm.hidden = true; // hide it
newForm.innerHTML = result; // put the html sent by the server inside the form
var action_url = $(newForm).find('input[name="data_set"]').data('actionUrl');
newForm.setAttribute("action", action_url); // set the action url
$(document.getElementsByTagName('body')[0]).append(newForm); // append the form to the body
$(newForm).find('input[data-remove-me]').remove(); // remove all the input that should be removed
if(action_url) {
newForm.submit(); // and finally submit the form
return $.Deferred();
}
}
}, function () {
else {
self.displayError(
_t('Server Error'),
_t("We are not able to redirect you to the payment form.")
);
}
}).fail(function (error, event) {
self.displayError(
_t('Server Error'),
_t("We are not able to delete your payment method at the moment.")
_t("We are not able to redirect you to the payment form. ") +
error.data.message
);
});
};
rpc.query({
model: 'payment.token',
method: 'get_linked_records',
args: [pm_id],
}).then(function (result) {
if (result[pm_id].length > 0) {
// if there's records linked to this payment method
var content = '';
result[pm_id].forEach(function (sub) {
content += '<p><a href="' + sub.url + '" title="' + sub.description + '">' + sub.name + '</a><p/>';
});
content = $('<div>').html(_t('<p>This card is currently linked to the following records:<p/>') + content);
// Then we display the list of the records and ask the user if he really want to remove the payment method.
new Dialog(self, {
title: _t('Warning!'),
size: 'medium',
$content: content,
buttons: [
{text: _t('Confirm Deletion'), classes: 'btn-primary', close: true, click: tokenDelete},
{text: _t('Cancel'), close: true}]
}).open();
}
else {
// if there's no records linked to this payment method, then we delete it
tokenDelete();
// we append the form to the body and send it.
this.displayError(
_t("Cannot set-up the payment"),
_t("We're unable to process your payment.")
);
}
}, function (err, event) {
self.displayError(
_t('Server Error'),
_t("We are not able to delete your payment method at the moment.") + err.data.message
);
});
},
// event handler when clicking on 'and more' to show more payment icon
onClickMorePaymentIcon: function (ev) {
ev.preventDefault();
ev.stopPropagation();
var $listItems = $(ev.currentTarget).parents('ul').children('li');
var $moreItem = $(ev.currentTarget).parents('li');
$listItems.removeClass('d-none');
$moreItem.addClass('d-none');
},
// event handler when clicking on a radio button
radioClickEvent: function (ev) {
// radio button checked when we click on entire zone(body) of the payment acquirer
$(ev.currentTarget).find('input[type="radio"]').prop("checked", true);
this.updateNewPaymentDisplayStatus();
},
updateNewPaymentDisplayStatus: function ()
{
var checked_radio = this.$('input[type="radio"]:checked');
// we hide all the acquirers form
this.$('[id*="o_payment_add_token_acq_"]').addClass('d-none');
this.$('[id*="o_payment_form_acq_"]').addClass('d-none');
if (checked_radio.length !== 1) {
return;
}
else { // if the user is using an old payment then we just submit the form
form.submit();
return $.Deferred();
}
}
else {
this.displayError(
_t('No payment method selected'),
_t('Please select a payment method.')
);
}
},
/**
* Called when clicking on the button to add a new payment method.
*
* @private
* @param {Event} ev
*/
addPmEvent: function (ev) {
ev.stopPropagation();
ev.preventDefault();
var checked_radio = this.$('input[type="radio"]:checked');
var self = this;
var button = ev.target;
// we check if the user has selected a 'add a new payment' option
if (checked_radio.length === 1 && this.isNewPaymentRadio(checked_radio[0])) {
// we retrieve which acquirer is used
checked_radio = checked_radio[0];
var acquirer_id = this.getAcquirerIdFromRadio(checked_radio);
var acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id);
// we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array
var inputs_form = $('input', acquirer_form);
var form_data = this.getFormData(inputs_form);
var ds = $('input[name="data_set"]', acquirer_form)[0];
var wrong_input = false;
// if we clicked on an add new payment radio, display its form
if (this.isNewPaymentRadio(checked_radio)) {
this.$('#o_payment_add_token_acq_' + acquirer_id).removeClass('d-none');
}
else if (this.isFormPaymentRadio(checked_radio)) {
this.$('#o_payment_form_acq_' + acquirer_id).removeClass('d-none');
}
},
isNewPaymentRadio: function (element) {
return $(element).data('s2s-payment') === 'True';
},
isFormPaymentRadio: function (element) {
return $(element).data('form-payment') === 'True';
},
getAcquirerIdFromRadio: function (element) {
return $(element).data('acquirer-id');
},
displayError: function (title, message) {
var $checkedRadio = this.$('input[type="radio"]:checked'),
acquirerID = this.getAcquirerIdFromRadio($checkedRadio[0]);
var $acquirerForm;
if (this.isNewPaymentRadio($checkedRadio[0])) {
$acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID);
}
else if (this.isFormPaymentRadio($checkedRadio[0])) {
$acquirerForm = this.$('#o_payment_form_acq_' + acquirerID);
}
if ($checkedRadio.length === 0) {
return new Dialog(null, {
title: _t('Error: ') + _.str.escapeHTML(title),
size: 'medium',
$content: "<p>" + (_.str.escapeHTML(message) || "") + "</p>" ,
buttons: [
{text: _t('Ok'), close: true}]}).open();
} else {
// removed if exist error message
this.$('#payment_error').remove();
var messageResult = '<div class="alert alert-danger mb4" id="payment_error">';
if (title != '') {
messageResult = messageResult + '<b>' + _.str.escapeHTML(title) + ':</b></br>';
inputs_form.toArray().forEach(function (element) {
//skip the check of non visible inputs
if ($(element).attr('type') == 'hidden') {
return true;
}
$(element).closest('div.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
$(element).siblings( ".o_invalid_field" ).remove();
//force check of forms validity (useful for Firefox that refill forms automatically on f5)
$(element).trigger("focusout");
if (element.dataset.isRequired && element.value.length === 0) {
$(element).closest('div.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>';
$(element).closest('div.form-group').append(message);
wrong_input = true;
}
else if ($(element).closest('div.form-group').hasClass('o_has_error')) {
wrong_input = true;
var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>';
$(element).closest('div.form-group').append(message);
}
messageResult = messageResult + _.str.escapeHTML(message) + '</div>';
$acquirerForm.append(messageResult);
}
},
getFormData: function ($form) {
var unindexed_array = $form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function (n, i) {
indexed_array[n.name] = n.value;
});
return indexed_array;
},
});
$(function () {
// TODO move this to another module, requiring dom_ready and rejecting
// the returned deferred to get the proper message
if (!$('.o_payment_form').length) {
console.log("DOM doesn't contain '.o_payment_form'");
return;
if (wrong_input) {
return;
}
// We add a 'processing' icon into the 'add a new payment' button
$(button).attr('disabled', true);
$(button).children('.fa-plus-circle').removeClass('fa-plus-circle');
$(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i>&nbsp;</span>');
// we force the check when adding a card trough here
form_data.verify_validity = true;
// do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route'
this._rpc({
route: ds.dataset.createRoute,
params: form_data,
}).then(function (data) {
// if the server has returned true
if (data.result) {
// and it need a 3DS authentication
if (data['3d_secure'] !== false) {
// then we display the 3DS page to the user
$("body").html(data['3d_secure']);
}
// if it doesn't require 3DS
else {
// we just go to the return_url or reload the page
if (form_data.return_url) {
window.location = form_data.return_url;
}
else {
window.location.reload();
}
}
}
// if the server has returned false, we display an error
else {
if (data.error) {
self.displayError(
'',
data.error);
} else { // if the server doesn't provide an error message
self.displayError(
_t('Server Error'),
_t('e.g. Your credit card details are wrong. Please verify.'));
}
}
// here we remove the 'processing' icon from the 'add a new payment' button
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle');
$(button).find('span.o_loader').remove();
}).fail(function (error, event) {
// if the rpc fails, pretty obvious
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle');
$(button).find('span.o_loader').remove();
self.displayError(
_t('Server error'),
_t("We are not able to add your payment method at the moment.</p>") +
error.data.message
);
});
}
$('.o_payment_form').each(function () {
var $elem = $(this);
var form = new PaymentForm(null, $elem.data());
form.attachTo($elem);
});
});
else {
this.displayError(
_t('No payment method selected'),
_t('Please select the option to add a new payment method.')
);
}
},
/**
* Called when clicking on a button to delete a payment method.
*
* @private
* @param {Event} ev
*/
deletePmEvent: function (ev) {
ev.stopPropagation();
ev.preventDefault();
var self = this;
var pm_id = parseInt(ev.target.value);
return PaymentForm;
var tokenDelete = function () {
self._rpc({
model: 'payment.token',
method: 'unlink',
args: [pm_id],
}).then(function (result) {
if (result === true) {
ev.target.closest('div').remove();
}
}, function () {
self.displayError(
_t('Server Error'),
_t("We are not able to delete your payment method at the moment.")
);
});
};
this._rpc({
model: 'payment.token',
method: 'get_linked_records',
args: [pm_id],
}).then(function (result) {
if (result[pm_id].length > 0) {
// if there's records linked to this payment method
var content = '';
result[pm_id].forEach(function (sub) {
content += '<p><a href="' + sub.url + '" title="' + sub.description + '">' + sub.name + '</a><p/>';
});
content = $('<div>').html(_t('<p>This card is currently linked to the following records:<p/>') + content);
// Then we display the list of the records and ask the user if he really want to remove the payment method.
new Dialog(self, {
title: _t('Warning!'),
size: 'medium',
$content: content,
buttons: [
{text: _t('Confirm Deletion'), classes: 'btn-primary', close: true, click: tokenDelete},
{text: _t('Cancel'), close: true}]
}).open();
}
else {
// if there's no records linked to this payment method, then we delete it
tokenDelete();
}
}, function (err, event) {
self.displayError(
_t('Server Error'),
_t("We are not able to delete your payment method at the moment.") + err.data.message
);
});
},
/**
* Called when clicking on 'and more' to show more payment icon.
*
* @private
* @param {Event} ev
*/
onClickMorePaymentIcon: function (ev) {
ev.preventDefault();
ev.stopPropagation();
var $listItems = $(ev.currentTarget).parents('ul').children('li');
var $moreItem = $(ev.currentTarget).parents('li');
$listItems.removeClass('d-none');
$moreItem.addClass('d-none');
},
/**
* Called when clicking on a radio button.
*
* @private
* @param {Event} ev
*/
radioClickEvent: function (ev) {
// radio button checked when we click on entire zone(body) of the payment acquirer
$(ev.currentTarget).find('input[type="radio"]').prop("checked", true);
this.updateNewPaymentDisplayStatus();
},
});
return publicWidget.registry.PaymentForm;
});
+120 -38
View File
@@ -1,48 +1,130 @@
odoo.define('portal.portal', function (require) {
'use strict';
require('web.dom_ready');
var publicWidget = require('web.public.widget');
publicWidget.registry.portalDetails = publicWidget.Widget.extend({
selector: '.o_portal_details',
read_events: {
'change select[name="country_id"]': '_onCountryChange',
},
if (!$('.o_portal').length) {
return $.Deferred().reject("DOM doesn't contain '.o_portal'");
}
/**
* @override
*/
start: function () {
var def = this._super.apply(this, arguments);
if (this.editableMode) {
return;
}
if ($('.o_portal_details').length) {
var state_options = $("select[name='state_id']:enabled option:not(:first)");
$('.o_portal_details').on('change', "select[name='country_id']", function () {
var select = $("select[name='state_id']");
state_options.detach();
var displayed_state = state_options.filter("[data-country_id="+($(this).val() || 0)+"]");
var nb = displayed_state.appendTo(select).show().size();
select.parent().toggle(nb>=1);
});
$('.o_portal_details').find("select[name='country_id']").change();
}
this.$state = this.$('select[name="state_id"]');
this.$stateOptions = this.$state.filter(':enabled').find('option:not(:first)');
this._adaptAddressForm();
if ($('.o_portal_search_panel').length) {
$('.o_portal_search_panel .search-submit').click(function () {
var search = $.deparam(window.location.search.substring(1));
search.search_in = $(".o_portal_search_panel .dropdown-item.active").attr("href").replace("#","");
search.search = $(".o_portal_search_panel input[name='search']").val();
window.location.search = $.param(search);
});
return def;
},
$('.o_portal_search_panel .dropdown-menu').find('.dropdown-item').click(function (e) {
e.preventDefault();
$(this).parents('.dropdown-menu').find('.dropdown-item').removeClass('active');
$(this).closest('.dropdown-item').addClass('active');
var label = $(this).clone();
label.find('span.nolabel').remove();
$(".o_portal_search_panel span#search_label").text(label.text());
});
// init search label
$('.o_portal_search_panel .dropdown-menu').find('.dropdown-item.active').trigger('click');
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
$(".o_portal_search_panel input[name='search']").on('keyup', function (e) {
if (e.keyCode === 13) {
$('.o_portal_search_panel .search-submit').trigger('click');
}
});
}
/**
* @private
*/
_adaptAddressForm: function () {
var $country = this.$('select[name="country_id"]');
var countryID = ($country.val() || 0);
this.$stateOptions.detach();
var $displayedState = this.$stateOptions.filter('[data-country_id=' + countryID + ']');
var nb = $displayedState.appendTo(this.$state).show().size();
this.$state.parent().toggle(nb >= 1);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onCountryChange: function () {
this._adaptAddressForm();
},
});
publicWidget.registry.portalSearchPanel = publicWidget.Widget.extend({
selector: '.o_portal_search_panel',
read_events: {
'click .search-submit': '_onSearchSubmitClick',
'click .dropdown-item': '_onDropdownItemClick',
'keyup input[name="search"]': '_onSearchInputKeyup',
},
/**
* @override
*/
start: function () {
var def = this._super.apply(this, arguments);
if (this.editableMode) {
return;
}
this._adaptSearchLabel(this.$('.dropdown-item.active'));
return def;
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_adaptSearchLabel: function (elem) {
var $label = $(elem).clone();
$label.find('span.nolabel').remove();
this.$('input[name="search"]').attr('placeholder', $label.text().trim());
},
/**
* @private
*/
_search: function () {
var search = $.deparam(window.location.search.substring(1));
search['search_in'] = this.$('.dropdown-item.active').attr('href').replace('#', '');
search['search'] = this.$('input[name="search"]').val();
window.location.search = $.param(search);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onSearchSubmitClick: function () {
this._search();
},
/**
* @private
*/
_onDropdownItemClick: function (ev) {
ev.preventDefault();
var $item = $(ev.currentTarget);
$item.closest('.dropdown-menu').find('.dropdown-item').removeClass('active');
$item.addClass('active');
this._adaptSearchLabel(ev.currentTarget);
},
/**
* @private
*/
_onSearchInputKeyup: function (ev) {
if (ev.keyCode === $.ui.keyCode.ENTER) {
this._search();
}
},
});
});
+87 -69
View File
@@ -1,12 +1,8 @@
odoo.define('portal.chatter', function(require) {
odoo.define('portal.chatter', function (require) {
'use strict';
var base = require('web_editor.base');
var ajax = require('web.ajax');
var core = require('web.core');
var dom = require('web.dom');
var Widget = require('web.Widget');
var rpc = require('web.rpc');
var publicWidget = require('web.public.widget');
var time = require('web.time');
var qweb = core.qweb;
@@ -18,12 +14,17 @@ var _t = core._t;
* Display the composer (according to access right)
*
*/
var PortalComposer = Widget.extend({
var PortalComposer = publicWidget.Widget.extend({
template: 'portal.Composer',
xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'],
events: {
'click .o_portal_chatter_composer_btn': '_onSubmitButtonClick',
'click .o_portal_chatter_composer_btn': 'async _onSubmitButtonClick',
},
init: function(parent, options){
/**
* @constructor
*/
init: function (parent, options) {
this._super.apply(this, arguments);
this.options = _.defaults(options || {}, {
'allow_composer': true,
@@ -33,12 +34,12 @@ var PortalComposer = Widget.extend({
'res_model': false,
'res_id': false,
});
// TODO simplify this using the 'async' keyword in the events
// property definition as soon as this widget is converted in
// frontend widget.
this._onSubmitButtonClick = dom.makeButtonHandler(this._onSubmitButtonClick);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
@@ -56,20 +57,23 @@ var PortalComposer = Widget.extend({
* - Display chatter: pager, total message, composer (according to access right)
* - Provider API to filter displayed messages
*/
var PortalChatter = Widget.extend({
var PortalChatter = publicWidget.Widget.extend({
template: 'portal.Chatter',
xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'],
events: {
"click .o_portal_chatter_pager_btn": '_onClickPager',
},
xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'],
init: function(parent, options){
/**
* @constructor
*/
init: function (parent, options) {
var self = this;
this.options = {};
this._super.apply(this, arguments);
// underscorize the camelcased option keys
_.each(options, function(val, key) {
_.each(options, function (val, key) {
self.options[_.str.underscored(key)] = val;
});
// set default options
@@ -92,22 +96,16 @@ var PortalChatter = Widget.extend({
this.set('message_count', this.options['message_count']);
this.set('pager', {});
this.set('domain', this.options['domain']);
this._current_page = this.options['pager_start'];
this._currentPage = this.options['pager_start'];
},
willStart: function(){
var self = this;
// load qweb template and init data
/**
* @override
*/
willStart: function () {
return $.when(
rpc.query({
route: '/mail/chatter_init',
params: this._messageFetchPrepareParams()
}),
this._super.apply(this, arguments),
).then(function(result){
self.result = result;
self.options = _.extend(self.options, self.result['options'] || {});
return result;
});
this._chatterInit()
);
},
/**
* @override
@@ -115,9 +113,9 @@ var PortalChatter = Widget.extend({
start: function () {
// bind events
this.on("change:messages", this, this._renderMessages);
this.on("change:message_count", this, function(){
this.on("change:message_count", this, function () {
this._renderMessageCount();
this.set('pager', this._pager(this._current_page));
this.set('pager', this._pager(this._currentPage));
});
this.on("change:pager", this, this._renderPager);
this.on("change:domain", this, this._onChangeDomain);
@@ -149,12 +147,12 @@ var PortalChatter = Widget.extend({
* @param {Array} domain
* @returns {Deferred}
*/
messageFetch: function(domain){
messageFetch: function (domain) {
var self = this;
return rpc.query({
return this._rpc({
route: '/mail/chatter_fetch',
params: self._messageFetchPrepareParams()
}).then(function(result){
params: self._messageFetchPrepareParams(),
}).then(function (result) {
self.set('messages', self.preprocessMessages(result['messages']));
self.set('message_count', result['message_count']);
});
@@ -165,8 +163,8 @@ var PortalChatter = Widget.extend({
* @param {Array<Object>}
* @returns {Array}
*/
preprocessMessages: function(messages){
_.each(messages, function(m){
preprocessMessages: function (messages) {
_.each(messages, function (m) {
m['author_avatar_url'] = _.str.sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id);
m['published_date_str'] = _.str.sprintf(_t('Published on %s'), moment(time.str_to_datetime(m.date)).format('MMMM Do YYYY, h:mm:ss a'));
});
@@ -177,6 +175,21 @@ var PortalChatter = Widget.extend({
// Private
//--------------------------------------------------------------------------
/**
* @private
* @returns {Deferred}
*/
_chatterInit: function () {
var self = this;
return this._rpc({
route: '/mail/chatter_init',
params: this._messageFetchPrepareParams()
}).then(function (result) {
self.result = result;
self.options = _.extend(self.options, self.result['options'] || {});
return result;
});
},
/**
* Change the current page by refreshing current domain
*
@@ -184,26 +197,26 @@ var PortalChatter = Widget.extend({
* @param {Number} page
* @param {Array} domain
*/
_changeCurrentPage: function(page, domain){
this._current_page = page;
_changeCurrentPage: function (page, domain) {
this._currentPage = page;
var d = domain ? domain : _.clone(this.get('domain'));
this.set('domain', d); // trigger fetch message
},
_messageFetchPrepareParams: function(){
_messageFetchPrepareParams: function () {
var self = this;
var data = {
'res_model': this.options['res_model'],
'res_id': this.options['res_id'],
'limit': this.options['pager_step'],
'offset': (this._current_page-1) * this.options['pager_step'],
'offset': (this._currentPage - 1) * this.options['pager_step'],
'allow_composer': this.options['allow_composer'],
};
// add token field to allow to post comment without being logged
if(self.options['token']){
if (self.options['token']) {
data['token'] = self.options['token'];
}
// add domain
if(this.get('domain')){
if (this.get('domain')) {
data['domain'] = this.get('domain');
}
return data;
@@ -215,34 +228,34 @@ var PortalChatter = Widget.extend({
* @param {Number} page
* @returns {Object}
*/
_pager: function(page){
var page = page || 1;
_pager: function (page) {
page = page || 1;
var total = this.get('message_count');
var scope = this.options['pager_scope'];
var step = this.options['pager_step'];
// Compute Pager
var page_count = Math.ceil(parseFloat(total) / step);
var pageCount = Math.ceil(parseFloat(total) / step);
var page = Math.max(1, Math.min(parseInt(page), page_count));
page = Math.max(1, Math.min(parseInt(page), pageCount));
scope -= 1;
var pmin = Math.max(page - parseInt(Math.floor(scope/2)), 1);
var pmax = Math.min(pmin + scope, page_count);
var pmin = Math.max(page - parseInt(Math.floor(scope / 2)), 1);
var pmax = Math.min(pmin + scope, pageCount);
if(pmax - scope > 0){
if (pmax - scope > 0) {
pmin = pmax - scope;
}else{
} else {
pmin = 1;
}
var pages = [];
_.each(_.range(pmin, pmax+1), function(index){
_.each(_.range(pmin, pmax + 1), function (index) {
pages.push(index);
});
return {
"page_count": page_count,
"page_count": pageCount,
"offset": (page - 1) * step,
"page": page,
"page_start": pmin,
@@ -252,13 +265,13 @@ var PortalChatter = Widget.extend({
"pages": pages
};
},
_renderMessages: function(){
_renderMessages: function () {
this.$('.o_portal_chatter_messages').html(qweb.render("portal.chatter_messages", {widget: this}));
},
_renderMessageCount: function(){
_renderMessageCount: function () {
this.$('.o_message_counter').replaceWith(qweb.render("portal.chatter_message_count", {widget: this}));
},
_renderPager: function(){
_renderPager: function () {
this.$('.o_portal_chatter_pager').replaceWith(qweb.render("portal.pager", {widget: this}));
},
@@ -266,10 +279,10 @@ var PortalChatter = Widget.extend({
// Handlers
//--------------------------------------------------------------------------
_onChangeDomain: function(){
_onChangeDomain: function () {
var self = this;
this.messageFetch().then(function(){
var p = self._current_page;
this.messageFetch().then(function () {
var p = self._currentPage;
self.set('pager', self._pager(p));
});
},
@@ -277,24 +290,29 @@ var PortalChatter = Widget.extend({
* @private
* @param {MouseEvent} event
*/
_onClickPager: function(ev){
_onClickPager: function (ev) {
ev.preventDefault();
var page = $(ev.currentTarget).data('page');
this._changeCurrentPage(page);
},
});
base.ready().then(function () {
$('.o_portal_chatter').each(function (index) {
var $elem = $(this);
var mail_thread = new PortalChatter(null, $elem.data());
mail_thread.appendTo($elem);
});
publicWidget.registry.portalChatter = publicWidget.Widget.extend({
selector: '.o_portal_chatter',
/**
* @override
*/
start: function () {
var defs = [this._super.apply(this, arguments)];
var chatter = new PortalChatter(this, this.$el.data());
defs.push(chatter.appendTo(this.$el));
return $.when.apply($, defs);
},
});
return {
PortalComposer: PortalComposer,
PortalChatter: PortalChatter,
};
});
+9 -10
View File
@@ -1,21 +1,19 @@
odoo.define('portal.PortalSidebar', function (require) {
"use strict";
'use strict';
var config = require('web.config');
var core = require('web.core');
var Widget = require('web.Widget');
var publicWidget = require('web.public.widget');
var time = require('web.time');
var _t = core._t;
var PortalSidebar = Widget.extend({
var PortalSidebar = publicWidget.Widget.extend({
/**
* @override
*/
start: function () {
var self = this;
this._super.apply(this, arguments);
this._setDelayLabel();
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
@@ -25,9 +23,10 @@ var PortalSidebar = Widget.extend({
/**
* Set the due/delay information according to the given date
* like : <span class="o_portal_sidebar_timeago" t-att-datetime="invoice.date_due"/>
*
* @private
*/
_setDelayLabel : function () {
_setDelayLabel: function () {
var $sidebarTimeago = this.$el.find('.o_portal_sidebar_timeago');
_.each($sidebarTimeago, function (el) {
var dateTime = moment(time.auto_str_to_date($(el).attr('datetime'))),
@@ -35,7 +34,7 @@ var PortalSidebar = Widget.extend({
diff = dateTime.diff(today, 'days', true),
displayStr;
if (diff === 0){
if (diff === 0) {
displayStr = _t('Due today');
} else if (diff > 0) {
displayStr = _.str.sprintf(_t('Due in %d days'), Math.abs(diff));
@@ -54,10 +53,10 @@ var PortalSidebar = Widget.extend({
// open a new window with pdf for print in Firefox (in other system: http://printjs.crabbly.com)
if ($.browser.mozilla) {
window.open(href, '_blank');
return ;
return;
}
if (!this.printContent) {
this.printContent = $('<iframe id="print_iframe_content" src="'+ href +'" style="display:none"></iframe>');
this.printContent = $('<iframe id="print_iframe_content" src="' + href + '" style="display:none"></iframe>');
this.$el.append(this.printContent);
this.printContent.on('load', function () {
$(this).get(0).contentWindow.print();
+183 -185
View File
@@ -1,195 +1,193 @@
odoo.define('portal.signature_form', function (require) {
'use strict';
var core = require('web.core');
var dom = require('web.dom');
var NameAndSignature = require('web.name_and_signature').NameAndSignature;
var qweb = core.qweb;
var Widget = require('web.Widget');
var _t = core._t;
/**
* This widget is a signature request form. It uses
* @see NameAndSignature for the input fields, adds a submit
* button, and handles the RPC to save the result.
*/
var SignatureForm = Widget.extend({
template: 'portal.portal_signature',
xmlDependencies: ['/portal/static/src/xml/portal_signature.xml'],
events: {
'click .o_portal_sign_submit': '_onClickSignSubmit',
},
custom_events: {
'signature_changed': '_onChangeSignature',
},
/**
* Overridden to allow options.
*
* @constructor
* @param {Widget} parent
* @param {Object} options
* @param {string} options.callUrl - make RPC to this url
* @param {string} [options.sendLabel='Accept & Sign'] - label of the
* send button
* @param {Object} [options.rpcParams={}] - params for the RPC
* @param {Object} [options.nameAndSignatureOptions={}] - options for
* @see NameAndSignature.init()
*/
init: function (parent, options) {
this._super.apply(this, arguments);
this.csrf_token = odoo.csrf_token;
this.callUrl = options.callUrl || '';
this.rpcParams = options.rpcParams || {};
this.sendLabel = options.sendLabel || _t("Accept & Sign");
this.nameAndSignature = new NameAndSignature(this,
options.nameAndSignatureOptions || {});
// debounce on click to not make multiple RPC if clicking too fast
this._onClickSignSubmit = dom.makeButtonHandler(this._onClickSignSubmit);
},
/**
* Overridden to get the DOM elements
* and to insert the name and signature.
*
* @override
*/
start: function () {
this.$confirm_btn = this.$('.o_portal_sign_submit');
this.$controls = this.$('.o_portal_sign_controls');
this.nameAndSignature.replace(this.$('.o_web_sign_name_and_signature'));
return this._super.apply(this, arguments);
},
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
/**
* Focuses the name.
*
* @see NameAndSignature.focusName();
*/
focusName: function () {
this.nameAndSignature.focusName();
},
/**
* Resets the signature.
*
* @see NameAndSignature.resetSignature();
*/
resetSignature: function () {
return this.nameAndSignature.resetSignature();
},
//----------------------------------------------------------------------
// Handlers
//----------------------------------------------------------------------
/**
* Handles click on the submit button.
*
* This will get the current name and signature and validate them.
* If they are valid, they are sent to the server, and the reponse is
* handled. If they are invalid, it will display the errors to the user.
*
* @private
* @param {Event} ev
* @returns {Deferred}
*/
_onClickSignSubmit: function (ev) {
var self = this;
ev.preventDefault();
if (!this.nameAndSignature.validateSignature()) {
return;
}
var name = this.nameAndSignature.getName();
var signature = this.nameAndSignature.getSignatureImage()[1];
return this._rpc({
route: this.callUrl,
params: _.extend(this.rpcParams, {
'name': name,
'signature': signature,
}),
}).then(function (data) {
if (data.error) {
self.$('.o_portal_sign_error_msg').remove();
self.$controls.prepend(qweb.render('portal.portal_signature_error', {widget: data}));
} else if (data.success) {
var $success = qweb.render('portal.portal_signature_success', {widget: data});
self.$el.empty().append($success);
}
if (data.force_refresh) {
if (data.redirect_url) {
window.location = data.redirect_url;
} else {
window.location.reload();
}
// no resolve if we reload the page
return $.Deferred();
}
});
},
/**
* Toggles the submit button depending on the signature state.
*
* @private
*/
_onChangeSignature: function () {
var isEmpty = this.nameAndSignature.isSignatureEmpty();
this.$confirm_btn.prop('disabled', isEmpty);
},
});
return {
SignatureForm: SignatureForm,
};
});
odoo.define('portal.signature_form_init', function (require) {
'use strict';
var SignatureForm = require('portal.signature_form').SignatureForm;
var rootWidget = require('root.widget');
require('web.dom_ready');
var core = require('web.core');
var publicWidget = require('web.public.widget');
var NameAndSignature = require('web.name_and_signature').NameAndSignature;
var qweb = core.qweb;
$('.o_portal_signature_form').each(function () {
var hasBeenReset = false;
var $elem = $(this);
var _t = core._t;
var callUrl = $elem.data('call-url');
var nameAndSignatureOptions = {
defaultName: $elem.data('default-name'),
mode: $elem.data('mode'),
displaySignatureRatio: $elem.data('signature-ratio'),
signatureType: $elem.data('signature-type'),
};
var sendLabel = $elem.data('send-label');
/**
* This widget is a signature request form. It uses
* @see NameAndSignature for the input fields, adds a submit
* button, and handles the RPC to save the result.
*/
var SignatureForm = publicWidget.Widget.extend({
template: 'portal.portal_signature',
xmlDependencies: ['/portal/static/src/xml/portal_signature.xml'],
events: {
'click .o_portal_sign_submit': 'async _onClickSignSubmit',
},
custom_events: {
'signature_changed': '_onChangeSignature',
},
var form = new SignatureForm(rootWidget, {
callUrl: callUrl,
nameAndSignatureOptions: nameAndSignatureOptions,
sendLabel: sendLabel,
});
form.appendTo($elem);
// Correctly set up the signature area if it is inside a modal
$('.o_portal_signature_form').parents('.modal').on('shown.bs.modal', function (ev) {
if (!hasBeenReset) {
// Reset it only the first time it is open to get correct
// size. After we want to keep its content on reopen.
hasBeenReset = true;
form.resetSignature();
} else {
form.focusName();
/**
* Overridden to allow options.
*
* @constructor
* @param {Widget} parent
* @param {Object} options
* @param {string} options.callUrl - make RPC to this url
* @param {string} [options.sendLabel='Accept & Sign'] - label of the
* send button
* @param {Object} [options.rpcParams={}] - params for the RPC
* @param {Object} [options.nameAndSignatureOptions={}] - options for
* @see NameAndSignature.init()
*/
init: function (parent, options) {
this._super.apply(this, arguments);
this.csrf_token = odoo.csrf_token;
this.callUrl = options.callUrl || '';
this.rpcParams = options.rpcParams || {};
this.sendLabel = options.sendLabel || _t("Accept & Sign");
this.nameAndSignature = new NameAndSignature(this,
options.nameAndSignatureOptions || {});
},
/**
* Overridden to get the DOM elements
* and to insert the name and signature.
*
* @override
*/
start: function () {
this.$confirm_btn = this.$('.o_portal_sign_submit');
this.$controls = this.$('.o_portal_sign_controls');
this.nameAndSignature.replace(this.$('.o_web_sign_name_and_signature'));
return this._super.apply(this, arguments);
},
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
/**
* Focuses the name.
*
* @see NameAndSignature.focusName();
*/
focusName: function () {
this.nameAndSignature.focusName();
},
/**
* Resets the signature.
*
* @see NameAndSignature.resetSignature();
*/
resetSignature: function () {
return this.nameAndSignature.resetSignature();
},
//----------------------------------------------------------------------
// Handlers
//----------------------------------------------------------------------
/**
* Handles click on the submit button.
*
* This will get the current name and signature and validate them.
* If they are valid, they are sent to the server, and the reponse is
* handled. If they are invalid, it will display the errors to the user.
*
* @private
* @param {Event} ev
* @returns {Deferred}
*/
_onClickSignSubmit: function (ev) {
var self = this;
ev.preventDefault();
if (!this.nameAndSignature.validateSignature()) {
return;
}
});
var name = this.nameAndSignature.getName();
var signature = this.nameAndSignature.getSignatureImage()[1];
return this._rpc({
route: this.callUrl,
params: _.extend(this.rpcParams, {
'name': name,
'signature': signature,
}),
}).then(function (data) {
if (data.error) {
self.$('.o_portal_sign_error_msg').remove();
self.$controls.prepend(qweb.render('portal.portal_signature_error', {widget: data}));
} else if (data.success) {
var $success = qweb.render('portal.portal_signature_success', {widget: data});
self.$el.empty().append($success);
}
if (data.force_refresh) {
if (data.redirect_url) {
window.location = data.redirect_url;
} else {
window.location.reload();
}
// no resolve if we reload the page
return $.Deferred();
}
});
},
/**
* Toggles the submit button depending on the signature state.
*
* @private
*/
_onChangeSignature: function () {
var isEmpty = this.nameAndSignature.isSignatureEmpty();
this.$confirm_btn.prop('disabled', isEmpty);
},
});
publicWidget.registry.SignatureForm = publicWidget.Widget.extend({
selector: '.o_portal_signature_form',
/**
* @private
*/
start: function () {
var hasBeenReset = false;
var callUrl = this.$el.data('call-url');
var nameAndSignatureOptions = {
defaultName: this.$el.data('default-name'),
mode: this.$el.data('mode'),
displaySignatureRatio: this.$el.data('signature-ratio'),
signatureType: this.$el.data('signature-type'),
};
var sendLabel = this.$el.data('send-label');
var form = new SignatureForm(this, {
callUrl: callUrl,
nameAndSignatureOptions: nameAndSignatureOptions,
sendLabel: sendLabel,
});
// Correctly set up the signature area if it is inside a modal
this.$el.closest('.modal').on('shown.bs.modal', function (ev) {
if (!hasBeenReset) {
// Reset it only the first time it is open to get correct
// size. After we want to keep its content on reopen.
hasBeenReset = true;
form.resetSignature();
} else {
form.focusName();
}
});
return $.when(
this._super.apply(this, arguments),
form.appendTo(this.$el)
);
},
});
return {
SignatureForm: SignatureForm,
};
});
+26 -21
View File
@@ -1,27 +1,32 @@
odoo.define('website_rating_project.rating', function (require) {
'use strict';
var time = require('web.time');
require('web.dom_ready');
var time = require('web.time');
var publicWidget = require('web.public.widget');
if(!$('.o_portal_project_rating').length) {
return $.Deferred().reject("DOM doesn't contain '.o_portal_project_rating'");
}
publicWidget.registry.ProjectRatingImage = publicWidget.Widget.extend({
selector: '.o_portal_project_rating .o_rating_image',
/**
* Rating popover with some informations
*/
$('.o_portal_project_rating .o_rating_image').popover({
placement: 'bottom',
trigger: 'hover',
html: 'true',
content: function () {
var id = $(this).data('id');
var rating_date = $(this).data('rating-date');
var base_date = time.auto_str_to_date(rating_date);
var duration = moment(base_date).fromNow();
$("#rating_"+ id).find(".rating_timeduration").text(duration);
return $("#rating_"+ id).html();
}
});
/**
* @override
*/
start: function () {
this.$el.popover({
placement: 'bottom',
trigger: 'hover',
html: 'true',
content: function () {
var $elem = $(this);
var id = $elem.data('id');
var ratingDate = $elem.data('rating-date');
var baseDate = time.auto_str_to_date(ratingDate);
var duration = moment(baseDate).fromNow();
var $rating = $('#rating_' + id);
$rating.find('.rating_timeduration').text(duration);
return $rating.html();
},
});
return this._super.apply(this, arguments);
},
});
});
@@ -1,43 +1,25 @@
odoo.define('sale.SalePortalSidebar.instance', function (require) {
"use strict";
require('web.dom_ready');
var SalePortalSidebar = require('sale.SalePortalSidebar');
if (!$('.o_portal_sale_sidebar').length) {
return $.Deferred().reject("DOM doesn't contain '.o_portal_sale_sidebar'");
}
var $spyWatch = $('body[data-target=".navspy"]'),
sale_portal_sidebar = new SalePortalSidebar($spyWatch);
return sale_portal_sidebar.attachTo($('.o_portal_sale_sidebar')).then(function () {
return sale_portal_sidebar;
});
});
//==============================================================================
odoo.define('sale.SalePortalSidebar', function (require) {
"use strict";
'use strict';
var publicWidget = require('web.public.widget');
var PortalSidebar = require('portal.PortalSidebar');
var SalePortalSidebar = PortalSidebar.extend({
publicWidget.registry.SalePortalSidebar = PortalSidebar.extend({
selector: '.o_portal_sale_sidebar',
/**
* @override
* @param {Object} $watched_selector
* @constructor
*/
init: function ($watched_selector) {
init: function (parent, options) {
this._super.apply(this, arguments);
this.authorizedTextTag = ['em', 'b', 'i', 'u'];
this.spyWatched = $watched_selector;
this.spyWatched = $('body[data-target=".navspy"]');
},
/**
* @override
*/
start: function () {
this._super.apply(this, arguments);
var def = this._super.apply(this, arguments);
var $spyWatcheElement = this.$el.find('[data-id="portal_sidebar"]');
this._setElementId($spyWatcheElement);
// Nav Menu ScrollSpy
@@ -47,6 +29,7 @@ var SalePortalSidebar = PortalSidebar.extend({
this.$('#o_sale_portal_paynow').trigger('click');
$.bbq.removeState('allow_payment');
}
return def;
},
//--------------------------------------------------------------------------
@@ -129,6 +112,4 @@ var SalePortalSidebar = PortalSidebar.extend({
return rawText.join(' ');
},
});
return SalePortalSidebar;
});
@@ -1,51 +1,47 @@
odoo.define('sale_management.sale_management', function (require) {
'use strict';
require('web.dom_ready');
var ajax = require('web.ajax');
var Widget = require('web.Widget');
var publicWidget = require('web.public.widget');
if (!$('.o_portal_sale_sidebar').length) {
return $.Deferred().reject("DOM doesn't contain '.o_portal_sale_sidebar'");
}
publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
selector: '.o_portal_sale_sidebar a.js_update_line_json',
events: {
'click': '_onClick',
},
// Add to SO button
var UpdateLineButton = Widget.extend({
events: {
'click' : 'onClick',
},
onClick: function (ev) {
ev.preventDefault();
var self = this;
var href = this.$el.attr("href");
var order_id = href.match(/my\/orders\/([0-9]+)/);
var line_id = href.match(/update_line\/([0-9]+)/);
var params = {
'line_id': line_id[1],
'remove': self.$el.is('[href*="remove"]'),
'unlink': self.$el.is('[href*="unlink"]'),
};
var token = href.match(/token=(.*)/);
if (token) {
params.access_token = token;
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onClick: function (ev) {
ev.preventDefault();
var self = this;
var href = this.$el.attr("href");
var orderID = href.match(/my\/orders\/([0-9]+)/);
var lineID = href.match(/update_line\/([0-9]+)/);
var params = {
'line_id': lineID[1],
'remove': self.$el.is('[href*="remove"]'),
'unlink': self.$el.is('[href*="unlink"]'),
};
var token = href.match(/token=(.*)/);
if (token) {
params['access_token'] = token;
}
var url = "/my/orders/" + parseInt(orderID[1]) + "/update_line";
this._rpc({
route: url,
params: params,
}).then(function (data) {
if (!data) {
window.location.reload();
}
var url = "/my/orders/" + parseInt(order_id[1]) + "/update_line";
ajax.jsonRpc(url, 'call', params).then(function (data) {
if (!data) {
window.location.reload();
}
self.$el.parents('.input-group:first').find('.js_quantity').val(data[0]);
$('[data-id="total_amount"]>span').html(data[1]);
});
return false;
},
});
var update_button_list = [];
$('a.js_update_line_json').each(function (index) {
var button = new UpdateLineButton();
button.setElement($(this)).start();
update_button_list.push(button);
});
self.$el.closest('.input-group').find('.js_quantity').val(data[0]);
$('[data-id="total_amount"] > span').html(data[1]);
});
},
});
});
@@ -1,24 +1,34 @@
odoo.define('web_unsplash.beacon', function (require) {
'use strict';
require('web.dom_ready');
var ajax = require('web.ajax');
var publicWidget = require('web.public.widget');
var unsplash_images = [];
_.each($('img[src*="/unsplash/"]'), function (img, index) {
// get image id from URL (`http://www.domain.com:1234/unsplash/xYdf5feoI/lion.jpg` -> `xYdf5feoI`)
unsplash_images.push(img.src.split('/unsplash/')[1].split('/')[0]);
});
if (unsplash_images.length) {
ajax.jsonRpc('/web_unsplash/get_app_id', 'call').then(function (appID) {
if (appID) {
var unsplash_view_url = 'https://views.unsplash.com/v';
$.get(unsplash_view_url, {
photo_id: unsplash_images.join(','),
app_id: appID,
publicWidget.registry.UnsplashBeacon = publicWidget.Widget.extend({
// /!\ To adapt the day the beacon makes sense for backend customizations
selector: '#wrapwrap',
/**
* @override
*/
start: function () {
var unsplashImages = _.map(this.$('img[src*="/unsplash/"]'), function (img) {
// get image id from URL (`http://www.domain.com:1234/unsplash/xYdf5feoI/lion.jpg` -> `xYdf5feoI`)
return img.src.split('/unsplash/')[1].split('/')[0];
});
if (unsplashImages.length) {
this._rpc({
route: '/web_unsplash/get_app_id',
}).then(function (appID) {
if (!appID) {
return;
}
$.get('https://views.unsplash.com/v', {
'photo_id': unsplashImages.join(','),
'app_id': appID,
});
});
}
});
}
return this._super.apply(this, arguments);
},
});
});
@@ -16,6 +16,7 @@ var WebsiteRoot = publicRootData.PublicRoot.extend({
'click .js_publish_management .js_publish_btn': '_onPublishBtnClick',
'click .js_multi_website_switch': '_onWebsiteSwitch',
'click .js_multi_company_switch': '_onCompanySwitch',
'shown.bs.modal': '_onModalShown',
}),
custom_events: _.extend({}, publicRootData.PublicRoot.prototype.custom_events || {}, {
'ready_to_clean_for_save': '_onAnimationStopDemand',
@@ -167,6 +168,13 @@ var WebsiteRoot = publicRootData.PublicRoot.extend({
window.location.reload(true);
});
},
/**
* @private
* @param {Event} ev
*/
_onModalShown: function (ev) {
$(ev.target).addClass('modal_shown');
},
});
return {
-9
View File
@@ -1,9 +0,0 @@
odoo.define('website.website_modal', function (require) {
'use strict';
require('web.dom_ready');
$('body').on('shown.bs.modal', function (e) {
$(e.target).addClass('modal_shown');
});
});
@@ -63,7 +63,6 @@
</xpath>
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/website/static/src/js/utils.js"/>
<script type="text/javascript" src="/website/static/src/js/website.js"/>
<script type="text/javascript" src="/website/static/src/js/content/website_root.js"/>
<script type="text/javascript" src="/website/static/src/js/content/compatibility.js"/>
@@ -1,20 +1,13 @@
odoo.define('website_mail.thread', function(require) {
odoo.define('website_mail.thread', function (require) {
'use strict';
var ajax = require('web.ajax');
var core = require('web.core');
var qweb = core.qweb;
var PortalChatter = require('portal.chatter').PortalChatter;
var portalChatter = require('portal.chatter');
/**
* Extends Frontend Chatter to handle rating
*/
PortalChatter.include({
_loadTemplates: function(){
return $.when(this._super(), ajax.loadXML('/website_mail/static/src/xml/website_mail.xml', qweb));
},
portalChatter.PortalChatter.include({
xmlDependencies: (portalChatter.PortalChatter.prototype.xmlDependencies || [])
.concat(['/website_mail/static/src/xml/website_mail.xml']),
});
});
@@ -1,335 +1,353 @@
odoo.define('website_rating.thread', function(require) {
'use strict';
odoo.define('website_rating.thread', function (require) {
'use strict';
var base = require('web_editor.base');
var ajax = require('web.ajax');
var core = require('web.core');
var Widget = require('web.Widget');
var rootWidget = require('web_editor.root_widget');
var core = require('web.core');
var publicWidget = require('web.public.widget');
var session = require('web.session');
var portalChatter = require('portal.chatter');
var session = require('web.session');
var _t = core._t;
var qweb = core.qweb;
var _t = core._t;
var PortalComposer = portalChatter.PortalComposer;
var PortalChatter = portalChatter.PortalChatter;
var PortalComposer = require('portal.chatter').PortalComposer;
var PortalChatter = require('portal.chatter').PortalChatter;
var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db rating range (1-10)
var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db rating range (1-10)
/**
* PortalComposer
*
* Extends Frontend Composer to handle rating submission
*/
PortalComposer.include({
events: _.extend({}, PortalComposer.prototype.events, {
"mousemove .stars i": "_onMoveStar",
"mouseleave .stars i": "_onMoveOutStar",
"click .stars": "_onClickStar",
"mouseleave .stars": "_onMouseleaveStar",
}),
/**
* PortalComposer
*
* Extends Frontend Composer to handle rating submission
* @constructor
*/
PortalComposer.include({
events: _.extend({}, PortalComposer.prototype.events, {
"mousemove .stars i" : "_onMoveStar",
"mouseleave .stars i" : "_onMoveOutStar",
"click .stars" : "_onClickStar",
"mouseleave .stars" : "_onMouseleaveStar",
}),
init: function (parent, options) {
this._super.apply(this, arguments);
init: function(parent, options){
this._super.apply(this, arguments);
// apply ratio to default rating value
if (options.default_rating_value) {
options.default_rating_value = parseFloat(options.default_rating_value) / STAR_RATING_RATIO;
}
// apply ratio to default rating value
if (options.default_rating_value) {
options.default_rating_value = parseFloat(options.default_rating_value) / STAR_RATING_RATIO;
}
// default options
this.options = _.defaults(this.options, {
'default_message': false,
'default_message_id': false,
'default_rating_value': false,
'force_submit_url': false,
});
// star input widget
this.labels = {
'0': "",
'1': _t("I hate it"),
'2': _t("I don't like it"),
'3': _t("It's okay"),
'4': _t("I like it"),
'5': _t("I love it"),
};
this.user_click = false; // user has click or not
this.set("star_value", this.options.default_rating_value);
this.on("change:star_value", this, this._onChangeStarValue);
},
/**
* @override
*/
start: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
// rating stars
self.$input = self.$('input[name="rating_value"]');
self.$star_list = self.$('.stars').find('i');
self.set("star_value", self.options.default_rating_value); // set the default value to trigger the display of star widget
});
},
// default options
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onClickStar: function () {
this.user_click = true;
this.$input.val(this.get("star_value") * STAR_RATING_RATIO);
},
/**
* @private
*/
_onChangeStarValue: function () {
var val = this.get("star_value");
var index = Math.floor(val);
var decimal = val - index;
// reset the stars
this.$star_list.removeClass('fa-star fa-star-half-o').addClass('fa-star-o');
this.$('.stars').find("i:lt(" + index + ")").removeClass('fa-star-o fa-star-half-o').addClass('fa-star');
if (decimal) {
this.$('.stars').find("i:eq(" + index + ")").removeClass('fa-star-o fa-star fa-star-half-o').addClass('fa-star-half-o');
}
this.$('.rate_text .badge').text(this.labels[index]);
},
/**
* @private
*/
_onMouseleaveStar: function () {
this.$('.rate_text').hide();
},
/**
* @private
* @param {MouseEvent} ev
*/
_onMoveStar: function (ev) {
var index = this.$('.stars i').index(ev.currentTarget);
this.$('.rate_text').show();
this.set("star_value", index + 1);
},
/**
* @private
*/
_onMoveOutStar: function () {
if (!this.user_click) {
this.set("star_value", parseInt(this.$input.val()));
}
this.user_click = false;
},
});
/**
* PortalChatter
*
* Extends Frontend Chatter to handle rating
*/
PortalChatter.include({
events: _.extend({}, PortalChatter.prototype.events, {
"click .o_website_rating_select": "_onClickStarDomain",
"click .o_website_rating_select_text": "_onClickStarDomainReset",
}),
xmlDependencies: (PortalChatter.prototype.xmlDependencies || [])
.concat([
'/website_rating/static/src/xml/website_mail.xml',
'/website_rating/static/src/xml/portal_chatter.xml'
]),
/**
* @constructor
*/
init: function (parent, options) {
this._super.apply(this, arguments);
// options
if (!_.contains(this.options, 'display_rating')) {
this.options = _.defaults(this.options, {
'default_message': false,
'default_message_id': false,
'default_rating_value': false,
'force_submit_url': false,
'display_rating': false,
'rating_default_value': 0.0,
});
// star input widget
this.labels = {
'0': "",
'1': _t("I hate it"),
'2': _t("I don't like it"),
'3': _t("It's okay"),
'4': _t("I like it"),
'5': _t("I love it"),
};
this.user_click = false; // user has click or not
this.set("star_value", this.options.default_rating_value);
this.on("change:star_value", this, this._onChangeStarValue);
},
start: function(){
var self = this;
return this._super.apply(this, arguments).then(function(){
// rating stars
self.$input = self.$('input[name="rating_value"]');
self.$star_list = self.$('.stars').find('i');
self.set("star_value", self.options.default_rating_value); // set the default value to trigger the display of star widget
});
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {MouseEvent} event
*/
_onClickStar: function(e){
this.user_click = true;
this.$input.val(this.get("star_value") * STAR_RATING_RATIO);
},
/**
* @private
*/
_onChangeStarValue: function(){
var val = this.get("star_value");
var index = Math.floor(val);
var decimal = val - index;
// reset the stars
this.$star_list.removeClass('fa-star fa-star-half-o').addClass('fa-star-o');
this.$('.stars').find("i:lt("+index+")").removeClass('fa-star-o fa-star-half-o').addClass('fa-star');
if(decimal){
this.$('.stars').find("i:eq("+(index)+")").removeClass('fa-star-o fa-star fa-star-half-o').addClass('fa-star-half-o');
}
this.$('.rate_text .badge').text(this.labels[index]);
},
_onMouseleaveStar: function(e){
this.$('.rate_text').hide();
},
/**
* @private
* @param {MouseEvent} event
*/
_onMoveStar: function(e){
var index = this.$('.stars i').index(e.currentTarget);
this.$('.rate_text').show();
this.set("star_value", index+1);
},
/**
* @private
*/
_onMoveOutStar: function(){
if(!this.user_click){
this.set("star_value", parseInt(this.$input.val()));
}
this.user_click = false;
},
});
}
// rating card
this.set('rating_card_values', {});
this.set('rating_value', false);
this.on("change:rating_value", this, this._onChangeRatingDomain);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* PortalChatter
* Update the messages format
*
* Extends Frontend Chatter to handle rating
* @param {Array<Object>} messages
* @returns {Array}
*/
PortalChatter.include({
events: _.extend({}, PortalChatter.prototype.events, {
"click .o_website_rating_select": "_onClickStarDomain",
"click .o_website_rating_select_text": "_onClickStarDomainReset",
}),
xmlDependencies: (PortalChatter.prototype.xmlDependencies || []).concat(['/website_rating/static/src/xml/website_mail.xml', '/website_rating/static/src/xml/portal_chatter.xml']),
preprocessMessages: function (messages) {
var self = this;
messages = this._super.apply(this, arguments);
if (this.options['display_rating']) {
_.each(messages, function (m) {
m['rating_value'] = self.roundToHalf(m['rating_value'] / STAR_RATING_RATIO);
});
}
return messages;
},
/**
* Round the given value with a precision of 0.5.
*
* Examples:
* - 1.2 --> 1.0
* - 1.7 --> 1.5
* - 1.9 --> 2.0
*
* @param {Number} value
* @returns Number
**/
roundToHalf: function (value) {
var converted = parseFloat(value); // Make sure we have a number
var decimal = (converted - parseInt(converted, 10));
decimal = Math.round(decimal * 10);
if (decimal === 5) {
return (parseInt(converted, 10) + 0.5);
}
if ((decimal < 3) || (decimal > 7)) {
return Math.round(converted);
} else {
return (parseInt(converted, 10) + 0.5);
}
},
init: function(parent, options){
this._super.apply(this, arguments);
// options
if(!_.contains(this.options, 'display_rating')){
this.options = _.defaults(this.options, {
'display_rating': false,
'rating_default_value': 0.0,
});
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_chatterInit: function () {
var self = this;
return this._super.apply(this, arguments).then(function (result) {
if (!result['rating_stats']) {
return;
}
// rating card
this.set('rating_card_values', {});
this.set('rating_value', false);
this.on("change:rating_value", this, this._onChangeRatingDomain);
},
willStart: function(){
var self = this;
return this._super.apply(this, arguments).then(function(result){
// rating card
if(result['rating_stats']){
var rating_data = {
'avg': Math.round(result['rating_stats']['avg'] / STAR_RATING_RATIO * 100) / 100,
'percent': [],
};
_.each(_.keys(result['rating_stats']['percent']), function(rating){
if (rating % 2 == 0) { // is even number
rating_data['percent'].push({
'num': rating / STAR_RATING_RATIO,
'percent': result['rating_stats']['percent'][rating],
});
}
var ratingData = {
'avg': Math.round(result['rating_stats']['avg'] / STAR_RATING_RATIO * 100) / 100,
'percent': [],
};
_.each(_.keys(result['rating_stats']['percent']), function (rating) {
if (rating % 2 === 0) {
ratingData['percent'].push({
'num': rating / STAR_RATING_RATIO,
'percent': result['rating_stats']['percent'][rating],
});
self.set('rating_card_values', rating_data);
}
});
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Update the messages format
*
* @param {Array<Object>} messages
* @returns {Array}
*/
preprocessMessages: function(messages){
var self = this;
var messages = this._super.apply(this, arguments);
if(this.options['display_rating']){
_.each(messages, function(m){
m['rating_value'] = self.round_to_half(m['rating_value'] / STAR_RATING_RATIO);
});
}
return messages;
},
/**
* Round the given value with a precision of 0.5.
*
* Examples:
* - 1.2 --> 1.0
* - 1.7 --> 1.5
* - 1.9 --> 2.0
*
* @param {Number} value
* @returns Number
**/
round_to_half: function(value) {
var converted = parseFloat(value); // Make sure we have a number
var decimal = (converted - parseInt(converted, 10));
decimal = Math.round(decimal * 10);
if(decimal === 5){
return (parseInt(converted, 10)+0.5);
}
if((decimal < 3) || (decimal > 7)){
return Math.round(converted);
}else{
return (parseInt(converted, 10)+0.5);
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
_messageFetchPrepareParams: function(){
var params = this._super.apply(this, arguments);
if(this.options['display_rating']){
params['rating_include'] = true;
}
return params;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {MouseEvent} event
*/
_onClickStarDomain: function(e){
var $tr = this.$(e.currentTarget);
var num = $tr.data('star');
if($tr.css('opacity') == 1){
this.set('rating_value', num);
this.$('.o_website_rating_select').css({
'opacity': 0.5,
});
this.$('.o_website_rating_select_text[data-star="'+num+'"]').css({
'visibility': 'visible',
'opacity': 1,
});
this.$('.o_website_rating_select[data-star="'+num+'"]').css({
'opacity': 1,
});
}
},
/**
* @private
* @param {MouseEvent} event
*/
_onClickStarDomainReset: function(e){
e.stopPropagation();
e.preventDefault();
this.set('rating_value', false);
this.$('.o_website_rating_select_text').css('visibility', 'hidden');
this.$('.o_website_rating_select').css({
'opacity': 1,
});
},
/**
* @private
*/
_onChangeRatingDomain: function(){
var domain = [];
if(this.get('rating_value')){
domain = [['rating_value', '=', this.get('rating_value') * STAR_RATING_RATIO]];
}
this._changeCurrentPage(1, domain);
},
});
self.set('rating_card_values', ratingData);
});
},
/**
* @override
*/
_messageFetchPrepareParams: function () {
var params = this._super.apply(this, arguments);
if (this.options['display_rating']) {
params['rating_include'] = true;
}
return params;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* RatingPopupComposer
*
* Display the rating average with a static star widget, and open
* a popup with the portal composer when clicking on it.
**/
var RatingPopupComposer = Widget.extend({
template: 'website_rating.PopupComposer',
xmlDependencies: [
'/portal/static/src/xml/portal_chatter.xml',
'/website_rating/static/src/xml/website_mail.xml',
'/website_rating/static/src/xml/portal_chatter.xml',
],
init: function(parent, options){
this._super.apply(this, arguments);
this.rating_avg = Math.round(options['ratingAvg'] / STAR_RATING_RATIO * 100) / 100 || 0.0;
this.rating_total = options['ratingTotal'] || 0.0;
this.options = _.defaults({}, options, {
'token': false,
'res_model': false,
'res_id': false,
'pid': 0,
'display_composer': !session.is_website_user,
'display_rating': true,
'csrf_token': odoo.csrf_token,
'user_id': session.user_id,
* @private
* @param {MouseEvent} event
*/
_onClickStarDomain: function (e) {
var $tr = this.$(e.currentTarget);
var num = $tr.data('star');
if ($tr.css('opacity') === '1') {
this.set('rating_value', num);
this.$('.o_website_rating_select').css({
'opacity': 0.5,
});
},
/**
* @override
*/
start: function () {
var defs = [];
defs.push(this._super.apply(this, arguments));
// instanciate and insert composer widget
this._composer = new PortalComposer(this, this.options);
defs.push(this._composer.replace(this.$('.o_portal_chatter_composer')));
return $.when.apply($, defs);
},
});
base.ready().then(function () {
$('.o_rating_popup_composer').each(function (index) {
var $elem = $(this);
var rating_popup = new RatingPopupComposer(rootWidget, $elem.data());
rating_popup.appendTo($elem);
});
this.$('.o_website_rating_select_text[data-star="' + num + '"]').css({
'visibility': 'visible',
'opacity': 1,
});
this.$('.o_website_rating_select[data-star="' + num + '"]').css({
'opacity': 1,
});
}
},
/**
* @private
* @param {MouseEvent} event
*/
_onClickStarDomainReset: function (e) {
e.stopPropagation();
e.preventDefault();
this.set('rating_value', false);
this.$('.o_website_rating_select_text').css('visibility', 'hidden');
this.$('.o_website_rating_select').css({
'opacity': 1,
});
},
/**
* @private
*/
_onChangeRatingDomain: function () {
var domain = [];
if (this.get('rating_value')) {
domain = [['rating_value', '=', this.get('rating_value') * STAR_RATING_RATIO]];
}
this._changeCurrentPage(1, domain);
},
});
/**
* RatingPopupComposer
*
* Display the rating average with a static star widget, and open
* a popup with the portal composer when clicking on it.
**/
var RatingPopupComposer = publicWidget.Widget.extend({
template: 'website_rating.PopupComposer',
xmlDependencies: [
'/portal/static/src/xml/portal_chatter.xml',
'/website_rating/static/src/xml/website_mail.xml',
'/website_rating/static/src/xml/portal_chatter.xml',
],
init: function (parent, options) {
this._super.apply(this, arguments);
this.rating_avg = Math.round(options['ratingAvg'] / STAR_RATING_RATIO * 100) / 100 || 0.0;
this.rating_total = options['ratingTotal'] || 0.0;
this.options = _.defaults({}, options, {
'token': false,
'res_model': false,
'res_id': false,
'pid': 0,
'display_composer': !session.is_website_user,
'display_rating': true,
'csrf_token': odoo.csrf_token,
'user_id': session.user_id,
});
},
/**
* @override
*/
start: function () {
var defs = [];
defs.push(this._super.apply(this, arguments));
// instanciate and insert composer widget
this._composer = new PortalComposer(this, this.options);
defs.push(this._composer.replace(this.$('.o_portal_chatter_composer')));
return $.when.apply($, defs);
},
});
publicWidget.registry.RatingPopupComposer = publicWidget.Widget.extend({
selector: '.o_rating_popup_composer',
/**
* @override
*/
start: function () {
var ratingPopup = new RatingPopupComposer(this, this.$el.data());
return $.when(
this._super.apply(this, arguments),
ratingPopup.appendTo(this.$el)
);
},
});
});
@@ -1,18 +1,48 @@
odoo.define('website_sale.payment', function (require) {
'use strict';
require('web.dom_ready');
var publicWidget = require('web.public.widget');
var $checkbox = $("#checkbox_cgv");
if (!$checkbox.length) {
return;
}
var $pay_button = $('button#o_payment_form_pay');
publicWidget.registry.WebsiteSalePayment = publicWidget.Widget.extend({
selector: '#wrapwrap:has(#checkbox_cgv)',
events: {
'change #checkbox_cgv': '_onCGVCheckboxClick',
},
$checkbox.on('change', function (ev) {
$pay_button.data('disabled_reasons', $pay_button.data('disabled_reasons') || {});
$pay_button.data('disabled_reasons').cgv = !ev.target.checked;
$pay_button.prop('disabled', _.contains($pay_button.data('disabled_reasons'), true));
/**
* @override
*/
start: function () {
this.$checkbox = this.$('#checkbox_cgv');
this.$payButton = $('button#o_payment_form_pay');
this._adaptPayButton();
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_adaptPayButton: function () {
var disabledReasons = this.$payButton.data('disabled_reasons') || {};
disabledReasons.cgv = !this.$checkbox.prop('checked');
this.$payButton.data('disabled_reasons', disabledReasons);
this.$payButton.prop('disabled', _.contains(disabledReasons, true));
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onCGVCheckboxClick: function () {
this._adaptPayButton();
},
});
$checkbox.change();
});