[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:
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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"/> ');
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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> </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> </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> </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;
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user