diff --git a/addons/auth_totp_portal/static/src/js/totp_frontend.js b/addons/auth_totp_portal/static/src/js/totp_frontend.js
index 3449ac82d6c..7bc1067da8c 100644
--- a/addons/auth_totp_portal/static/src/js/totp_frontend.js
+++ b/addons/auth_totp_portal/static/src/js/totp_frontend.js
@@ -57,9 +57,7 @@ function fromField(f, record) {
clipboard.on('success', function () {
clipboard.destroy();
$(copyButton).tooltip('show');
- _.delay(function () {
- $(copyButton).tooltip("hide");
- }, 800);
+ setTimeout(() => $(copyButton).tooltip("hide"), 800);
});
clipboard.on('error', function (e) {
clipboard.destroy();
diff --git a/addons/board/static/src/board_view.js b/addons/board/static/src/board_view.js
index 431978a99b9..5968839b695 100644
--- a/addons/board/static/src/board_view.js
+++ b/addons/board/static/src/board_view.js
@@ -5,6 +5,7 @@ import { registry } from "@web/core/registry";
import { BoardController } from "./board_controller";
import { XMLParser } from "@web/core/utils/xml";
import { Domain } from "@web/core/domain";
+import { escape } from "@web/core/utils/strings";
export class BoardArchParser extends XMLParser {
parse(arch, customViewId) {
@@ -36,7 +37,7 @@ export class BoardArchParser extends XMLParser {
const isFolded = Boolean(
node.hasAttribute("fold") ? parseInt(node.getAttribute("fold"), 10) : 0
);
- let action = {
+ const action = {
id: nextId++,
title: node.getAttribute("string"),
actionId: parseInt(node.getAttribute("name"), 10),
@@ -45,10 +46,12 @@ export class BoardArchParser extends XMLParser {
isFolded,
};
if (node.hasAttribute("domain")) {
- action.domain = new Domain(_.unescape(node.getAttribute("domain"))).toList();
+ action.domain = new Domain(
+ _.unescape(node.getAttribute("domain"))
+ ).toList();
// so it can be serialized when reexporting board xml
// we unescape before re-escaping, to avoid double escaping due to subsequent layout change
- action.domain.toString = () => _.escape(_.unescape(node.getAttribute("domain")));
+ action.domain.toString = () => escape(node.getAttribute("domain"));
}
archInfo.columns[currentIndex].actions.push(action);
break;
diff --git a/addons/im_livechat/static/src/legacy/core_models/device.js b/addons/im_livechat/static/src/legacy/core_models/device.js
index 69ae1906e50..f30234f75af 100644
--- a/addons/im_livechat/static/src/legacy/core_models/device.js
+++ b/addons/im_livechat/static/src/legacy/core_models/device.js
@@ -4,13 +4,14 @@ import { attr, Model } from "@im_livechat/legacy/model";
import { browser } from "@web/core/browser/browser";
import { isMobileOS } from "@web/core/browser/feature_detection";
import { SIZES } from "@web/core/ui/ui_service";
+import { debounce } from "@web/core/utils/timing";
Model({
name: "Device",
lifecycleHooks: {
_created() {
this._refresh();
- this._onResize = _.debounce(() => this._refresh(), 100);
+ this._onResize = debounce(() => this._refresh(), 100);
},
_willDelete() {
browser.removeEventListener("resize", this._onResize);
diff --git a/addons/im_livechat/static/src/legacy/legacy_models/public_livechat.js b/addons/im_livechat/static/src/legacy/legacy_models/public_livechat.js
index 57960c8bc41..86e96b29609 100644
--- a/addons/im_livechat/static/src/legacy/legacy_models/public_livechat.js
+++ b/addons/im_livechat/static/src/legacy/legacy_models/public_livechat.js
@@ -102,7 +102,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
});
}
- if (_.isBoolean(params.data.folded)) {
+ if (typeof params.data.folded === "boolean") {
this.messaging.publicLivechatGlobal.publicLivechat.update({
isFolded: params.data.folded,
});
@@ -207,7 +207,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
* @returns {boolean}
*/
hasMessages() {
- return !_.isEmpty(this.getMessages());
+ return Object.keys(this.getMessages() || {}).length > 0;
},
/**
* Tells if someone other than current user is typing something on this
@@ -216,7 +216,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
* @returns {boolean}
*/
isSomeoneTyping() {
- return !_.isEmpty(this._typingPartnerIDs);
+ return Object.keys(this._typingPartnerIDs || {}).length > 0;
},
/**
* Mark the thread as read, which resets the unread counter to 0. This is
@@ -269,7 +269,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
timeoutCallbackArguments: [partnerID],
timerID: partnerID,
});
- if (_.contains(this._typingPartnerIDs, partnerID)) {
+ if (this._typingPartnerIDs.includes(partnerID)) {
return;
}
this._typingPartnerIDs.push(partnerID);
@@ -327,7 +327,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, {
unregisterTyping(params) {
const partnerID = params.partnerID;
this._othersTypingTimers.unregisterTimer({ timerID: partnerID });
- if (!_.contains(this._typingPartnerIDs, partnerID)) {
+ if (!this._typingPartnerIDs.includes(partnerID)) {
return;
}
this._typingPartnerIDs = this._typingPartnerIDs.filter((id) => id !== partnerID);
diff --git a/addons/im_livechat/static/src/legacy/public_models/chatbot.js b/addons/im_livechat/static/src/legacy/public_models/chatbot.js
index a09f05f168f..8537de4b32f 100644
--- a/addons/im_livechat/static/src/legacy/public_models/chatbot.js
+++ b/addons/im_livechat/static/src/legacy/public_models/chatbot.js
@@ -1,6 +1,7 @@
/** @odoo-module **/
import { attr, one, clear, increment, Model } from "@im_livechat/legacy/model";
+import { debounce } from "@web/core/utils/timing";
import { qweb } from "web.core";
import { Markup } from "web.utils";
@@ -410,7 +411,7 @@ Model({
debouncedAwaitUserInput: attr({
compute() {
// debounced to let the user type several sentences, see 'Chatbot/awaitUserInput' for details
- return _.debounce(this.awaitUserInput, this.awaitUserInputDebounceTime);
+ return debounce(this.awaitUserInput, this.awaitUserInputDebounceTime);
},
}),
hasRestartButton: attr({
diff --git a/addons/im_livechat/static/src/legacy/public_models/livechat_button_view.js b/addons/im_livechat/static/src/legacy/public_models/livechat_button_view.js
index 7ea79b5a56e..59ba9bc8555 100644
--- a/addons/im_livechat/static/src/legacy/public_models/livechat_button_view.js
+++ b/addons/im_livechat/static/src/legacy/public_models/livechat_button_view.js
@@ -1,6 +1,7 @@
/** @odoo-module **/
import { attr, clear, one, Model } from "@im_livechat/legacy/model";
+import { debounce } from "@web/core/utils/timing";
import { getCookie, deleteCookie } from "web.utils.cookies";
@@ -480,7 +481,7 @@ Model({
}),
openChatDebounced: attr({
compute() {
- return _.debounce(this._openChat, 200, true);
+ return debounce(this._openChat, 200, true);
},
}),
publicLivechatGlobalOwner: one("PublicLivechatGlobal", {
diff --git a/addons/im_livechat/static/src/legacy/public_models/public_livechat_global.js b/addons/im_livechat/static/src/legacy/public_models/public_livechat_global.js
index b2998e09970..fc726e7ccc2 100644
--- a/addons/im_livechat/static/src/legacy/public_models/public_livechat_global.js
+++ b/addons/im_livechat/static/src/legacy/public_models/public_livechat_global.js
@@ -17,7 +17,7 @@ Model({
if (pageHistory) {
urlHistory = JSON.parse(pageHistory) || [];
}
- if (!_.contains(urlHistory, page)) {
+ if (!urlHistory.includes(page)) {
urlHistory.push(page);
while (urlHistory.length > this.HISTORY_LIMIT) {
urlHistory.shift();
diff --git a/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.js b/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.js
index 17b542431a5..02ada9f4aa5 100644
--- a/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.js
+++ b/addons/im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.js
@@ -1,6 +1,7 @@
/** @odoo-module **/
import * as mailUtils from "@mail/js/utils";
+import { debounce } from "@web/core/utils/timing";
import core from "web.core";
import time from "web.time";
@@ -324,7 +325,7 @@ const PublicLivechatView = Widget.extend({
* @param {string} options.model
* @param {integer} options.id
*/
- _redirect: _.debounce(
+ _redirect: debounce(
function (options) {
if ("channelID" in options) {
this.trigger("redirect_to_channel", options.channelID);
diff --git a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js
index b86a05dee04..967efb9f3c8 100644
--- a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js
+++ b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js
@@ -6,6 +6,7 @@ import Widget from "web.Widget";
import { unaccent } from "web.utils";
import { setCookie } from "web.utils.cookies";
+import { debounce } from "@web/core/utils/timing";
/**
* This is the widget that represent windows of livechat in the frontend.
@@ -35,7 +36,7 @@ const PublicLivechatWindow = Widget.extend({
this._super(parent);
this.messaging = messaging;
- this._debouncedOnScroll = _.debounce(this._onScroll.bind(this), 100);
+ this._debouncedOnScroll = debounce(this._onScroll.bind(this), 100);
},
/**
* @override
@@ -175,7 +176,7 @@ const PublicLivechatWindow = Widget.extend({
* Otherwise, fold/unfold the window if set/unset.
*/
toggleFold(folded) {
- if (!_.isBoolean(folded)) {
+ if (typeof folded !== "boolean") {
folded = !this.messaging.publicLivechatGlobal.publicLivechat.isFolded;
}
this.messaging.publicLivechatGlobal.publicLivechat.update({ isFolded: folded });
@@ -327,7 +328,7 @@ const PublicLivechatWindow = Widget.extend({
ev.stopPropagation(); // to prevent jquery's blockUI to cancel event
// ENTER key (avoid requiring jquery ui for external livechat)
if (ev.which === 13) {
- const content = _.str.trim(this.$input.val());
+ const content = String(this.$input.val() || "").trim();
const messageData = {
content,
attachment_ids: [],
diff --git a/addons/l10n_fr_pos_cert/static/src/js/pos.js b/addons/l10n_fr_pos_cert/static/src/js/pos.js
index 1edc8148419..f3bc8e575f2 100644
--- a/addons/l10n_fr_pos_cert/static/src/js/pos.js
+++ b/addons/l10n_fr_pos_cert/static/src/js/pos.js
@@ -17,7 +17,7 @@ patch(PosGlobalState.prototype, "l10n_fr_pos_cert.PosGlobalState", {
});
return false;
}
- return _.contains(french_countries, this.company.country.code);
+ return french_countries.includes(this.company.country.code);
},
disallowLineQuantityChange() {
const result = this._super(...arguments);
diff --git a/addons/mail/static/src/js/utils.js b/addons/mail/static/src/js/utils.js
index 8e94c665351..fa51a663a9e 100644
--- a/addons/mail/static/src/js/utils.js
+++ b/addons/mail/static/src/js/utils.js
@@ -40,11 +40,13 @@ function parseAndTransform(htmlString, transformFunction) {
* @return {string}
*/
function _parseAndTransform(nodes, transformFunction) {
- return _.map(nodes, function (node) {
- return transformFunction(node, function () {
- return _parseAndTransform(node.childNodes, transformFunction);
- });
- }).join("");
+ return Array.from($(nodes))
+ .map((node) => {
+ return transformFunction(node, function () {
+ return _parseAndTransform(node.childNodes, transformFunction);
+ });
+ })
+ .join("");
}
/**
@@ -85,16 +87,19 @@ function linkify(text, attrs) {
if (attrs.target === "_blank") {
attrs.rel = "noreferrer noopener";
}
- attrs = _.map(attrs, function (value, key) {
- return key + '="' + _.escape(value) + '"';
- }).join(" ");
+ attrs = Object.keys(attrs || {})
+ .map((key) => {
+ const value = attrs[key];
+ return `${key}="${escape(value)}"`;
+ })
+ .join(" ");
let curIndex = 0;
let result = "";
let match;
while ((match = urlRegexp.exec(text)) !== null) {
result += _escapeEntities(text.slice(curIndex, match.index));
const url = match[0];
- const href = !/^https?:\/\//i.test(url) ? "http://" + _.escape(url) : _.escape(url);
+ const href = !/^https?:\/\//i.test(url) ? "http://" + encodeURI(url) : encodeURI(url);
result += "' + _escapeEntities(url) + "";
curIndex = match.index + match[0].length;
}
@@ -175,11 +180,11 @@ function parseEmail(text) {
if (text) {
var result = text.match(/"?(.*?)"? <(.*@.*)>/);
if (result) {
- return [_.str.trim(result[1]), _.str.trim(result[2])];
+ return [(result[1] || "").trim(), (result[2] || "").trim()];
}
result = text.match(/(.*@.*)/);
if (result) {
- return [_.str.trim(result[1]), _.str.trim(result[1])];
+ return [String(result[1] || "").trim(), String(result[1] || "").trim()];
}
return [text, false];
}
diff --git a/addons/mail/static/tests/helpers/mock_server/models/mail_activity.js b/addons/mail/static/tests/helpers/mock_server/models/mail_activity.js
index 7fdef784444..79984b8380e 100644
--- a/addons/mail/static/tests/helpers/mock_server/models/mail_activity.js
+++ b/addons/mail/static/tests/helpers/mock_server/models/mail_activity.js
@@ -115,7 +115,7 @@ patch(MockServer.prototype, "mail/models/mail_activity", {
const records = this.getRecords(res_model, domain);
const activityTypes = this.getRecords("mail.activity.type", []);
- const activityIds = _.pluck(records, "activity_ids").flat();
+ const activityIds = records.map((x) => x.activity_ids).flat();
const groupedActivities = {};
const resIdToDeadline = {};
@@ -157,7 +157,7 @@ patch(MockServer.prototype, "mail/models/mail_activity", {
count: group.__count,
state: state,
o_closest_deadline: group.date_deadline,
- ids: _.pluck(activites, "id"),
+ ids: activites.map((x) => x.id),
};
});
@@ -177,9 +177,12 @@ patch(MockServer.prototype, "mail/models/mail_activity", {
}
return [type.id, type.display_name, mailTemplates];
}),
- activity_res_ids: _.sortBy(_.pluck(records, "id"), function (id) {
- return moment(resIdToDeadline[id]);
- }),
+ activity_res_ids: _.sortBy(
+ records.map((x) => x.id),
+ function (id) {
+ return moment(resIdToDeadline[id]);
+ }
+ ),
grouped_activities: groupedActivities,
};
},
diff --git a/addons/mail/static/tests/mail_utils_tests.js b/addons/mail/static/tests/mail_utils_tests.js
index 059abd627ea..1ba95a72ca3 100644
--- a/addons/mail/static/tests/mail_utils_tests.js
+++ b/addons/mail/static/tests/mail_utils_tests.js
@@ -18,6 +18,7 @@ QUnit.test("add_link utility function", function (assert) {
"https://tenor.com/view/chỗgiặt-dog-smile-gif-13860250": true,
"http://www.boîtenoire.be": true,
};
+
for (const [content, willLinkify] of Object.entries(testInputs)) {
const output = utils.parseAndTransform(content, utils.addLink);
if (willLinkify) {
diff --git a/addons/mass_mailing/static/src/js/mailing_portal.js b/addons/mass_mailing/static/src/js/mailing_portal.js
index 7e1d612e563..417cc0a2131 100644
--- a/addons/mass_mailing/static/src/js/mailing_portal.js
+++ b/addons/mass_mailing/static/src/js/mailing_portal.js
@@ -4,7 +4,7 @@
import ajax from "web.ajax";
import core from "web.core";
import "web.dom_ready";
- import { sprintf } from "@web/core/utils/strings";
+ import { sprintf, escape } from "@web/core/utils/strings";
var _t = core._t;
@@ -48,7 +48,7 @@ if ($('.o_unsubscribe_form').length) {
if (unsubscribed_list){
$('#subscription_info').html(sprintf(
_t("You have been successfully unsubscribed from %s."),
- _.escape(unsubscribed_list)
+ escape(unsubscribed_list)
));
}
else{
diff --git a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js
index 1bf755c1e55..913a70dc639 100644
--- a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js
+++ b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js
@@ -262,7 +262,7 @@ export class MassMailingHtmlField extends HtmlField {
device.isMobile ?
$themes.filter(theme => !$(theme).data("hideFromMobile")) :
$themes;
- this._themeParams = _.map(displayableThemes, (theme) => {
+ this._themeParams = Array.from(displayableThemes).map((theme) => {
const $theme = $(theme);
const name = $theme.data("name");
const classname = "o_" + name + "_theme";
diff --git a/addons/payment/static/src/js/checkout_form.js b/addons/payment/static/src/js/checkout_form.js
index a078e628b7e..ca2d6b96d5a 100644
--- a/addons/payment/static/src/js/checkout_form.js
+++ b/addons/payment/static/src/js/checkout_form.js
@@ -3,6 +3,7 @@
import publicWidget from "web.public.widget";
import paymentFormMixin from "payment.payment_form_mixin";
+ import { debounce } from "@web/core/utils/timing";
publicWidget.registry.PaymentCheckoutForm = publicWidget.Widget.extend(paymentFormMixin, {
selector: 'form[name="o_payment_checkout"]',
@@ -19,7 +20,7 @@
*/
init: function () {
const preventDoubleClick = handlerMethod => {
- return _.debounce(handlerMethod, 500, true);
+ return debounce(handlerMethod, 500, true);
};
this._super(...arguments);
// Prevent double-clicks and browser glitches on all inputs
diff --git a/addons/payment/static/src/js/manage_form.js b/addons/payment/static/src/js/manage_form.js
index 0eb04900d6d..c0a6f56b93c 100644
--- a/addons/payment/static/src/js/manage_form.js
+++ b/addons/payment/static/src/js/manage_form.js
@@ -5,6 +5,7 @@
import Dialog from "web.Dialog";
import paymentFormMixin from "payment.payment_form_mixin";
+import { debounce } from "@web/core/utils/timing";
const _t = core._t;
@@ -24,7 +25,7 @@
*/
init: function () {
const preventDoubleClick = handlerMethod => {
- return _.debounce(handlerMethod, 500, true);
+ return debounce(handlerMethod, 500, true);
};
this._super(...arguments);
// Prevent double-clicks and browser glitches on all inputs
diff --git a/addons/payment/static/src/js/payment_form_mixin.js b/addons/payment/static/src/js/payment_form_mixin.js
index 8bafa44180d..d2ff5f1c66e 100644
--- a/addons/payment/static/src/js/payment_form_mixin.js
+++ b/addons/payment/static/src/js/payment_form_mixin.js
@@ -5,6 +5,26 @@
const _t = core._t;
+ /**
+ * Replace HTML character with ASCII character
+ *
+ * @param {string} str string
+ * @returns {string} ASCII string
+ */
+ function escapeHTML(str) {
+ const htmlCaracters = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ "'": "'",
+ '"': """,
+ };
+ for (const [key, value] of Object.entries(htmlCaracters)) {
+ str = String(str).replace(new RegExp(key, "g"), value);
+ }
+ return str;
+ }
+
export default {
/**
@@ -76,7 +96,7 @@
return new Dialog(null, {
title: _.str.sprintf(_t("Error: %s"), title),
size: 'medium',
- $content: `
${_.str.escapeHTML(description) || ''}
`,
+ $content: `${escapeHTML(description) || ''}
`,
buttons: [{text: _t("Ok"), close: true}]
}).open();
} else { // Show error in inline form
@@ -84,12 +104,12 @@
// Build the html for the error
let errorHtml = `
- ${_.str.escapeHTML(title)}`;
+ ${escapeHTML(title)}`;
if (description !== '') {
- errorHtml += `${_.str.escapeHTML(description)}`;
+ errorHtml += `${escapeHTML(description)}`;
}
if (error !== '') {
- errorHtml += `${_.str.escapeHTML(error)}`;
+ errorHtml += `${escapeHTML(error)}`;
}
errorHtml += '
';
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
index ef50ddd0468..64d3a32bfff 100644
--- a/addons/point_of_sale/static/src/js/db.js
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -230,7 +230,7 @@ export const PosDB = core.Class.extend({
},
add_packagings: function (product_packagings) {
var self = this;
- _.map(product_packagings, function (product_packaging) {
+ Object.values(product_packagings || {}).map((product_packaging) => {
const products = Object.values(self.product_by_id);
if (products.find(({ id }) => id === product_packaging.product_id[0])) {
self.product_packaging_by_barcode[product_packaging.barcode] = product_packaging;
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 1bd84a9026d..b2a4a6e70a9 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -1011,7 +1011,7 @@ export class PosGlobalState extends PosModel {
// Keep the order ids that are about to be sent to the
// backend. In between create_from_ui and the success callback
// new orders may have been added to it.
- var order_ids_to_sync = _.pluck(orders, "id");
+ var order_ids_to_sync = orders.map((o) => o.id);
for (const order of orders) {
order.to_invoice = options.to_invoice || false;
@@ -1590,10 +1590,9 @@ export class Product extends PosModel {
let draftPackLotLines, weight, description, packLotLinesToEdit;
if (this.attribute_line_ids.some((id) => id in this.pos.attributes_by_ptal_id)) {
- const attributes = _.map(
- this.attribute_line_ids,
- (id) => this.pos.attributes_by_ptal_id[id]
- ).filter((attr) => attr !== undefined);
+ const attributes = this.attribute_line_ids
+ .map((id) => this.pos.attributes_by_ptal_id[id])
+ .filter((attr) => attr !== undefined);
const { confirmed, payload } = await this.pos.env.services.popup.add(
ProductConfiguratorPopup,
{
@@ -2172,15 +2171,7 @@ export class Orderline extends PosModel {
price_subtotal_incl: this.get_price_with_tax(),
discount: this.get_discount(),
product_id: this.get_product().id,
- tax_ids: [
- [
- 6,
- false,
- _.map(this.get_applicable_taxes(), function (tax) {
- return tax.id;
- }),
- ],
- ],
+ tax_ids: [[6, false, this.get_applicable_taxes().map((tax) => tax.id)]],
id: this.id,
pack_lot_ids: pack_lot_ids,
description: this.description,
@@ -2441,7 +2432,7 @@ export class Orderline extends PosModel {
if (line_taxes.length && line_taxes[0].price_include) {
new_included_taxes = new_included_taxes.concat(line_taxes);
}
- if (tax.price_include && !_.contains(line_taxes, tax)) {
+ if (tax.price_include && !line_taxes.includes(tax)) {
mapped_included_taxes.push(tax);
}
});
@@ -2521,7 +2512,7 @@ export class Packlotline extends PosModel {
}
set_lot_name(name) {
- this.lot_name = _.str.trim(name) || null;
+ this.lot_name = String(name || "").trim() || null;
}
get_lot_name() {
@@ -2836,11 +2827,9 @@ export class Order extends PosModel {
return orderLines.push([0, 0, item.export_as_JSON()]);
});
paymentLines = [];
- this.paymentlines.forEach(
- _.bind(function (item) {
- return paymentLines.push([0, 0, item.export_as_JSON()]);
- }, this)
- );
+ this.paymentlines.forEach((item) => {
+ return paymentLines.push([0, 0, item.export_as_JSON()]);
+ });
var json = {
name: this.get_name(),
amount_paid: this.get_total_paid() - this.get_change(),
diff --git a/addons/portal/static/src/js/portal_composer.js b/addons/portal/static/src/js/portal_composer.js
index 70276392bb5..de02036b661 100644
--- a/addons/portal/static/src/js/portal_composer.js
+++ b/addons/portal/static/src/js/portal_composer.js
@@ -147,8 +147,8 @@ var PortalComposer = publicWidget.Widget.extend({
_prepareMessageData: function () {
return Object.assign(this.options || {}, {
'message': this.$('textarea[name="message"]').val(),
- 'attachment_ids': _.pluck(this.attachments, 'id'),
- 'attachment_tokens': _.pluck(this.attachments, 'access_token'),
+ attachment_ids: this.attachments.map((a) => a.id),
+ attachment_tokens: this.attachments.map((a) => a.access_token),
});
},
/**
diff --git a/addons/portal_rating/static/src/js/portal_chatter.js b/addons/portal_rating/static/src/js/portal_chatter.js
index 801d0b0b1fa..326ff99f6d9 100644
--- a/addons/portal_rating/static/src/js/portal_chatter.js
+++ b/addons/portal_rating/static/src/js/portal_chatter.js
@@ -32,7 +32,7 @@ PortalChatter.include({
init: function (parent, options) {
this._super.apply(this, arguments);
// options
- if (!_.contains(this.options, 'display_rating')) {
+ if (!Object.keys(this.options).includes("display_rating")) {
this.options = _.defaults(this.options, {
'display_rating': false,
'rating_default_value': 0.0,
diff --git a/addons/pos_loyalty/static/src/js/Loyalty.js b/addons/pos_loyalty/static/src/js/Loyalty.js
index d0f854aefda..39543497e9d 100644
--- a/addons/pos_loyalty/static/src/js/Loyalty.js
+++ b/addons/pos_loyalty/static/src/js/Loyalty.js
@@ -377,7 +377,7 @@ patch(Order.prototype, "pos_loyalty.Order", {
},
wait_for_push_order() {
return (
- !_.isEmpty(this.couponPointChanges) ||
+ Object.keys(this.couponPointChanges || {}).length > 0 ||
this._has_gift_card_product() ||
this._get_reward_lines().length ||
this._super(...arguments)
@@ -931,12 +931,11 @@ patch(Order.prototype, "pos_loyalty.Order", {
continue;
}
let price_to_use = line.get_price_with_tax();
- if (program.program_type === 'gift_card') {
+ if (program.program_type === "gift_card") {
price_to_use = line.price;
}
const pointsPerUnit = round_precision(
- (rule.reward_point_amount * price_to_use) /
- line.get_quantity(),
+ (rule.reward_point_amount * price_to_use) / line.get_quantity(),
0.01
);
if (pointsPerUnit > 0) {
diff --git a/addons/pos_loyalty/static/src/js/PaymentScreen.js b/addons/pos_loyalty/static/src/js/PaymentScreen.js
index 4bd3fa5590c..f6b5b671c08 100644
--- a/addons/pos_loyalty/static/src/js/PaymentScreen.js
+++ b/addons/pos_loyalty/static/src/js/PaymentScreen.js
@@ -33,7 +33,7 @@ patch(PaymentScreen.prototype, "pos_loyalty.PaymentScreen", {
return;
}
// No need to do an rpc if no existing coupon is being used.
- if (!_.isEmpty(pointChanges) || newCodes.length) {
+ if (Object.keys(pointChanges || {}).length > 0 || newCodes.length) {
try {
const { successful, payload } = await this.orm.call(
"pos.order",
@@ -121,7 +121,7 @@ patch(PaymentScreen.prototype, "pos_loyalty.PaymentScreen", {
return true;
})
);
- if (!_.isEmpty(couponData)) {
+ if (Object.keys(couponData || []).length > 0) {
const payload = await this.orm.call("pos.order", "confirm_coupon_programs", [
server_ids,
couponData,
diff --git a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js
index 99dbf4d8e72..c6930d426f2 100644
--- a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js
+++ b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js
@@ -15,6 +15,7 @@ import { Table } from "./table";
import { usePos } from "@point_of_sale/app/pos_hook";
import { useService } from "@web/core/utils/hooks";
import { Component, onPatched, onMounted, onWillUnmount, useRef, useState } from "@odoo/owl";
+import { sprintf } from "@web/core/utils/strings";
export class FloorScreen extends Component {
static components = { EditableTable, EditBar, Table };
@@ -98,7 +99,7 @@ export class FloorScreen extends Component {
_onDeselectTable() {
this.state.selectedTableIds = [];
}
- async _createTableHelper(copyTable, duplicateFloor=false) {
+ async _createTableHelper(copyTable, duplicateFloor = false) {
const existingTable = this.activeFloor.tables;
let newTable;
if (copyTable) {
@@ -226,9 +227,13 @@ export class FloorScreen extends Component {
this.activeFloor.table_ids.push(newTable.id);
return newTable;
}
- _getNewTableName() {
+ _getNewTableName() {
let firstNum = 1;
- const tablesNameNumber = this.activeTables.map(table => +table.name).sort(function(a, b){return a-b});
+ const tablesNameNumber = this.activeTables
+ .map((table) => +table.name)
+ .sort(function (a, b) {
+ return a - b;
+ });
for (let i = 0; i < tablesNameNumber.length; i++) {
if (tablesNameNumber[i] == firstNum) {
@@ -262,7 +267,9 @@ export class FloorScreen extends Component {
//This will lead in receiving information on orders from a table that we do not have.
//If we do not have a table in the PoS but still receive information about it, we reload the tables
//from the server.
- const result = await this.orm.call("pos.session", "get_pos_ui_restaurant_floor", [[odoo.pos_session_id]]);
+ const result = await this.orm.call("pos.session", "get_pos_ui_restaurant_floor", [
+ [odoo.pos_session_id],
+ ]);
if (this.env.pos.config.is_table_management) {
this.env.pos.floors = result;
this.env.pos.loadRestaurantFloor();
@@ -279,8 +286,10 @@ export class FloorScreen extends Component {
table_obj.order_count = table.orders + unsynced_orders;
}
}
- get activeFloor() {
- return this.state.selectedFloorId ? this.env.pos.floors_by_id[this.state.selectedFloorId] : null;
+ get activeFloor() {
+ return this.state.selectedFloorId
+ ? this.env.pos.floors_by_id[this.state.selectedFloorId]
+ : null;
}
get activeTables() {
return this.activeFloor ? this.activeFloor.tables : null;
@@ -368,7 +377,11 @@ export class FloorScreen extends Component {
if (!confirmed) {
return;
}
- const floor = await this.orm.call("restaurant.floor", "create_from_ui", [newName, "#ACADAD",this.env.pos.config.id]);
+ const floor = await this.orm.call("restaurant.floor", "create_from_ui", [
+ newName,
+ "#ACADAD",
+ this.env.pos.config.id,
+ ]);
this.env.pos.floors_by_id[floor.id] = floor;
this.env.pos.floors.push(floor);
this.selectFloor(floor);
@@ -386,10 +399,14 @@ export class FloorScreen extends Component {
const floor = this.activeFloor;
const tables = this.activeFloor.tables;
const newFloorName = floor.name + " (copy)";
- const newFloor = await this.orm.call("restaurant.floor", "create_from_ui", [newFloorName, floor.background_color, this.env.pos.config.id]);
+ const newFloor = await this.orm.call("restaurant.floor", "create_from_ui", [
+ newFloorName,
+ floor.background_color,
+ this.env.pos.config.id,
+ ]);
this.env.pos.floors_by_id[newFloor.id] = newFloor;
this.env.pos.floors.push(newFloor);
- this.selectFloor(newFloor)
+ this.selectFloor(newFloor);
for (const table of tables) {
await this._createTableHelper(table, true);
}
@@ -458,8 +475,7 @@ export class FloorScreen extends Component {
selectedTable.seats = newSeatsNum;
await this._save(selectedTable);
}
- })
-
+ });
}
async changeToCircle() {
await this.changeShape("round");
@@ -474,7 +490,7 @@ export class FloorScreen extends Component {
this.selectedTables.forEach(async (selectedTable) => {
selectedTable.shape = form;
await this._save(selectedTable);
- })
+ });
this.render();
}
async setTableColor(color) {
@@ -482,7 +498,7 @@ export class FloorScreen extends Component {
selectedTables.forEach(async (selectedTable) => {
selectedTable.color = color;
await this._save(selectedTable);
- })
+ });
this.render();
this.state.isColorPicker = false;
}
@@ -500,8 +516,8 @@ export class FloorScreen extends Component {
async deleteFloorOrTable() {
if (this.selectedTables.length == 0) {
const { confirmed } = await this.popup.add(ConfirmPopup, {
- title: _.str.sprintf("Removing floor %s", this.activeFloor.name),
- body: _.str.sprintf(
+ title: `Removing floor ${this.activeFloor.name}`,
+ body: sprintf(
this.env._t("Removing a floor cannot be undone. Do you still wanna remove %s?"),
this.activeFloor.name
),
@@ -511,7 +527,8 @@ export class FloorScreen extends Component {
}
const originalSelectedFloorId = this.activeFloor.id;
await this.orm.call("restaurant.floor", "deactivate_floor", [
- originalSelectedFloorId, this.env.pos.pos_session.id
+ originalSelectedFloorId,
+ this.env.pos.pos_session.id,
]);
const floor = this.env.pos.floors_by_id[originalSelectedFloorId];
const orderList = [...this.env.pos.get_order_list()];
@@ -524,7 +541,9 @@ export class FloorScreen extends Component {
delete this.env.pos.tables_by_id[tableId];
});
delete this.env.pos.floors_by_id[originalSelectedFloorId];
- this.env.pos.floors = this.env.pos.floors.filter((floor) => floor.id != originalSelectedFloorId);
+ this.env.pos.floors = this.env.pos.floors.filter(
+ (floor) => floor.id != originalSelectedFloorId
+ );
this.env.pos.TICKET_SCREEN_STATE.syncedOrders.cache = {};
if (this.env.pos.floors.length > 0) {
this.selectFloor(this.env.pos.floors[0]);
@@ -558,17 +577,17 @@ export class FloorScreen extends Component {
await this.orm.call("restaurant.table", "create_from_ui", [
{ active: false, id: id },
]);
- this.activeFloor.tables = this.activeTables.filter(
- (table) => table.id !== id
- );
+ this.activeFloor.tables = this.activeTables.filter((table) => table.id !== id);
delete this.env.pos.tables_by_id[id];
} else {
await this.popup.add(ErrorPopup, {
title: this.env._t("Delete Error"),
- body: this.env._t("You cannot delete a table with orders still in draft for this table."),
+ body: this.env._t(
+ "You cannot delete a table with orders still in draft for this table."
+ ),
});
}
- })
+ });
// Value of an object can change inside async function call.
// Which means that in this code block, the value of `state.selectedTableId`
// before the await call can be different after the finishing the await call.
@@ -577,7 +596,7 @@ export class FloorScreen extends Component {
// else is selected during the rpc call.
const equalsCheck = (a, b) => {
return JSON.stringify(a) === JSON.stringify(b);
- }
+ };
if (equalsCheck(this.state.selectedTableIds, originalSelectedTableIds)) {
this.state.selectedTableIds = [];
}
diff --git a/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js b/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js
index 58dcc8401b3..750e6bd63ba 100644
--- a/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js
+++ b/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js
@@ -5,7 +5,7 @@ import { Order } from "@point_of_sale/js/models";
import { SplitOrderline } from "./SplitOrderline";
import { registry } from "@web/core/registry";
import { usePos } from "@point_of_sale/app/pos_hook";
-import { Component, useState, onMounted } from "@odoo/owl";
+import { Component, useState, onMounted } from "@odoo/owl";
export class SplitBillScreen extends Component {
static template = "SplitBillScreen";
@@ -46,7 +46,7 @@ export class SplitBillScreen extends Component {
this.pos.showScreen("ProductScreen");
}
proceed() {
- if (_.isEmpty(this.splitlines)) {
+ if (Object.keys(this.splitlines || {})?.length === 0) {
// Splitlines is empty
return;
}
diff --git a/addons/purchase/static/src/js/purchase_portal_sidebar.js b/addons/purchase/static/src/js/purchase_portal_sidebar.js
index 03f15ab5eeb..a5f1033367e 100644
--- a/addons/purchase/static/src/js/purchase_portal_sidebar.js
+++ b/addons/purchase/static/src/js/purchase_portal_sidebar.js
@@ -118,22 +118,19 @@ publicWidget.registry.PurchasePortalSidebar = PortalSidebar.extend({
_extractText: function ($node) {
var self = this;
var rawText = [];
- $node
- .contents()
- .toArray()
- .forEach((el) => {
- var current = $(el);
- if ($.trim(current.text())) {
- var tagName = current.prop("tagName");
- if (
- _.isUndefined(tagName) ||
- (!_.isUndefined(tagName) &&
- _.contains(self.authorizedTextTag, tagName.toLowerCase()))
- ) {
- rawText.push($.trim(current.text()));
- }
+ Array.from($node.contents()).forEach((el) => {
+ var current = $(el);
+ if ($.trim(current.text())) {
+ var tagName = current.prop("tagName");
+ if (
+ typeof tagName === "undefined" ||
+ (typeof tagName !== "undefined" &&
+ self.authorizedTextTag.includes(tagName.toLowerCase()))
+ ) {
+ rawText.push($.trim(current.text()));
}
- });
+ }
+ });
return rawText.join(" ");
},
});
diff --git a/addons/sale/static/src/js/sale_portal_sidebar.js b/addons/sale/static/src/js/sale_portal_sidebar.js
index 8a55538f71b..7b5872da523 100644
--- a/addons/sale/static/src/js/sale_portal_sidebar.js
+++ b/addons/sale/static/src/js/sale_portal_sidebar.js
@@ -105,7 +105,11 @@ publicWidget.registry.SalePortalSidebar = PortalSidebar.extend({
var current = $(el);
if ($.trim(current.text())) {
var tagName = current.prop("tagName");
- if (_.isUndefined(tagName) || (!_.isUndefined(tagName) && _.contains(self.authorizedTextTag, tagName.toLowerCase()))) {
+ if (
+ typeof tagName === "undefined" ||
+ (typeof tagName !== "undefined" &&
+ self.authorizedTextTag.includes(tagName.toLowerCase()))
+ ) {
rawText.push($.trim(current.text()));
}
}
diff --git a/addons/sale/static/src/js/variant_mixin.js b/addons/sale/static/src/js/variant_mixin.js
index 8e481e7bee5..f3097e548da 100644
--- a/addons/sale/static/src/js/variant_mixin.js
+++ b/addons/sale/static/src/js/variant_mixin.js
@@ -5,6 +5,8 @@ import core from "web.core";
import utils from "web.utils";
import ajax from "web.ajax";
import { sprintf } from "@web/core/utils/strings";
+import { memoize } from "@web/core/utils/functions";
+
var _t = core._t;
var VariantMixin = {
@@ -39,7 +41,7 @@ var VariantMixin = {
if (!$parent.data('uniqueId')) {
$parent.data('uniqueId', _.uniqueId());
}
- this._throttledGetCombinationInfo($parent.data('uniqueId'))(ev);
+ this._throttledGetCombinationInfo(this, $parent.data('uniqueId'))(ev);
},
/**
* @see onChangeVariant
@@ -523,7 +525,7 @@ var VariantMixin = {
$default_price.text(self._priceToStr(combination.list_price));
var isCombinationPossible = true;
- if (!_.isUndefined(combination.is_combination_possible)) {
+ if (typeof combination.is_combination_possible !== "undefined") {
isCombinationPossible = combination.is_combination_possible;
}
this._toggleDisable($parent, isCombinationPossible);
@@ -628,12 +630,10 @@ var VariantMixin = {
* @param {string} uniqueId
* @returns {function}
*/
- _throttledGetCombinationInfo: _.memoize(function (uniqueId) {
+ _throttledGetCombinationInfo: memoize(function (self, uniqueId) {
var dropMisordered = new concurrency.DropMisordered();
- var _getCombinationInfo = _.throttle(this._getCombinationInfo.bind(this), 500);
- return function (ev, params) {
- return dropMisordered.add(_getCombinationInfo(ev, params));
- };
+ var _getCombinationInfo = _.throttle(self._getCombinationInfo.bind(self), 500);
+ return (ev, params) => dropMisordered.add(_getCombinationInfo(ev, params));
}),
/**
* Toggles the disabled class depending on the $parent element
diff --git a/addons/sale_product_configurator/static/src/js/sale_product_field.js b/addons/sale_product_configurator/static/src/js/sale_product_field.js
index 1408efc59ae..4e96d2b1dd5 100644
--- a/addons/sale_product_configurator/static/src/js/sale_product_field.js
+++ b/addons/sale_product_configurator/static/src/js/sale_product_field.js
@@ -210,7 +210,7 @@ patch(SaleOrderLineProductField.prototype, 'sale_product_configurator', {
var noVariantAttributeValues = mainProduct.no_variant_attribute_values;
var noVariantCommands = [{ operation: "DELETE_ALL" }];
if (noVariantAttributeValues && noVariantAttributeValues.length !== 0) {
- var resIds = _.map(noVariantAttributeValues, function (noVariantValue) {
+ var resIds = noVariantAttributeValues.map((noVariantValue) => {
return { id: parseInt(noVariantValue.value) };
});
diff --git a/addons/survey/static/src/js/survey_session_manage.js b/addons/survey/static/src/js/survey_session_manage.js
index e248c525667..3ff31e8a6b2 100644
--- a/addons/survey/static/src/js/survey_session_manage.js
+++ b/addons/survey/static/src/js/survey_session_manage.js
@@ -116,9 +116,7 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend(SurveyPre
clipboard.on('success', function () {
clipboard.destroy();
$clipboardBtn.popover('show');
- _.delay(function () {
- $clipboardBtn.popover('hide');
- }, 800);
+ setTimeout(() => $clipboardBtn.popover('hide'), 800);
});
clipboard.on('error', function (e) {
diff --git a/addons/web/static/src/core/utils/numbers.js b/addons/web/static/src/core/utils/numbers.js
index 54b0405492a..8fd0ac71e82 100644
--- a/addons/web/static/src/core/utils/numbers.js
+++ b/addons/web/static/src/core/utils/numbers.js
@@ -16,15 +16,16 @@ export function clamp(num, min, max) {
* A function to create flexibly-numbered lists of integers, handy for each and map loops.
* step defaults to 1.
* Returns a list of integers from start (inclusive) to stop (exclusive), incremented (or decremented) by step.
- * @param {number} min default 0
- * @param {number} max
+ * @param {number} start default 0
+ * @param {number} stop
* @param {number} step default 1
* @returns {number[]}
*/
export function range(start, stop, step = 1) {
const array = [];
- for (let i = start; i < stop; i += step) {
- array.push(i);
+ const nsteps = Math.floor((stop - start) / step);
+ for (let i = 0; i < nsteps; i++) {
+ array.push(start + step * i);
}
return array;
}
diff --git a/addons/web/static/src/legacy/js/core/context.js b/addons/web/static/src/legacy/js/core/context.js
index 03c2d024d99..e9c59f60813 100644
--- a/addons/web/static/src/legacy/js/core/context.js
+++ b/addons/web/static/src/legacy/js/core/context.js
@@ -9,7 +9,7 @@ var Context = Class.extend({
this.__contexts = [];
this.__eval_context = null;
var self = this;
- Object.values(arguments).forEach((x) => self.add(x));
+ Object.values(arguments || {}).forEach((x) => self.add(x));
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/legacy/js/core/dialog.js b/addons/web/static/src/legacy/js/core/dialog.js
index ec803e51a25..3ba2ceefca1 100644
--- a/addons/web/static/src/legacy/js/core/dialog.js
+++ b/addons/web/static/src/legacy/js/core/dialog.js
@@ -137,7 +137,7 @@ var Dialog = Widget.extend({
self.$footer = self.$modal.find(".modal-footer");
self.set_buttons(self.buttons);
}
- self.$modal.on('hidden.bs.modal', _.bind(self.destroy, self));
+ self.$modal.on('hidden.bs.modal', self.destroy.bind(self));
});
},
/**
@@ -312,7 +312,7 @@ var Dialog = Widget.extend({
_setButtonsTo($target, buttons) {
var self = this;
$target.empty();
- buttons.forEach(buttonData => {
+ buttons.forEach((buttonData) => {
var $button = dom.renderButton({
attrs: {
class: buttonData.classes || (buttons.length > 1 ? 'btn-secondary' : 'btn-primary'),
diff --git a/addons/web/static/src/legacy/js/core/domain.js b/addons/web/static/src/legacy/js/core/domain.js
index 504144fb2cd..66daf5e9807 100644
--- a/addons/web/static/src/legacy/js/core/domain.js
+++ b/addons/web/static/src/legacy/js/core/domain.js
@@ -35,7 +35,7 @@ var Domain = collections.Tree.extend({
*/
init: function (domain, evalContext) {
this._super.apply(this, arguments);
- if (Array.isArray(domain) || _.isString(domain)) {
+ if (Array.isArray(domain) || typeof domain === "string") {
this._parse(this.normalizeArray(_.clone(this.stringToArray(domain, evalContext))));
} else {
this._data = !!domain;
@@ -87,10 +87,10 @@ var Domain = collections.Tree.extend({
switch (this._data[1]) {
case "=":
case "==":
- return _.isEqual(fieldValue, this._data[2]);
+ return JSON.stringify(fieldValue) === JSON.stringify(this._data[2]);
case "!=":
case "<>":
- return !_.isEqual(fieldValue, this._data[2]);
+ return JSON.stringify(fieldValue) !== JSON.stringify(this._data[2]);
case "<":
return (fieldValue < this._data[2]);
case ">":
@@ -156,9 +156,10 @@ var Domain = collections.Tree.extend({
return [];
} else {
var arr = [this._data];
- return arr.concat.apply(arr, _.map(this._children, function (child) {
- return child.toArray();
- }));
+ return arr.concat.apply(
+ arr,
+ this._children.map((child) => child.toArray())
+ );
}
},
/**
@@ -239,7 +240,7 @@ var Domain = collections.Tree.extend({
* @returns {string}
*/
arrayToString: function (domain) {
- if (_.isString(domain)) return domain;
+ if (typeof domain === "string") return domain;
function jsToPy(p) {
switch (p) {
@@ -267,7 +268,7 @@ var Domain = collections.Tree.extend({
* @returns {Array}
*/
stringToArray: function (domain, evalContext) {
- if (!_.isString(domain)) return _.clone(domain);
+ if (typeof domain !== "string") return _.clone(domain);
return pyUtils.eval("domain", domain ? domain.replace(/%%/g, '%') : "[]", evalContext);
},
/**
diff --git a/addons/web/static/src/legacy/js/core/misc.js b/addons/web/static/src/legacy/js/core/misc.js
index 95b6a71373b..4d24a24ec81 100644
--- a/addons/web/static/src/legacy/js/core/misc.js
+++ b/addons/web/static/src/legacy/js/core/misc.js
@@ -86,7 +86,9 @@ function blockUI() {
}
function unblockUI() {
- _.invoke(throbbers, 'destroy');
+ throbbers.forEach((throbber) => {
+ throbber.destroy();
+ });
throbbers = [];
$(document.body).removeClass('o_ui_blocked');
unblockAccessKeys();
diff --git a/addons/web/static/src/legacy/js/core/mixins.js b/addons/web/static/src/legacy/js/core/mixins.js
index ad0302e6c32..bbb4d367ca4 100644
--- a/addons/web/static/src/legacy/js/core/mixins.js
+++ b/addons/web/static/src/legacy/js/core/mixins.js
@@ -277,7 +277,7 @@ var EventDispatcherMixin = Object.assign({}, ParentedMixin, {
};
},
_delegateCustomEvents: function () {
- if (_.isEmpty(this.custom_events)) { return; }
+ if (Object.keys(this.custom_events || {}).length === 0) { return; }
for (var key in this.custom_events) {
if (!this.custom_events.hasOwnProperty(key)) { continue; }
diff --git a/addons/web/static/src/legacy/js/core/popover.js b/addons/web/static/src/legacy/js/core/popover.js
index 92ea2039c44..a0df16ee345 100644
--- a/addons/web/static/src/legacy/js/core/popover.js
+++ b/addons/web/static/src/legacy/js/core/popover.js
@@ -1,5 +1,6 @@
/** @odoo-module alias=web.Popover **/
+ import { debounce } from "@web/core/utils/timing";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { status, onWillUnmount, useEffect, useRef, useState } = owl;
@@ -29,7 +30,7 @@
this._onResizeWindow = this._onResizeWindow.bind(this);
this._onScrollDocument = _.throttle(this._onScrollDocument, 50);
- this._onResizeWindow = _.debounce(this._onResizeWindow, 250);
+ this._onResizeWindow = debounce(this._onResizeWindow, 250);
/**
* Those events are only necessary if the popover is currently open,
diff --git a/addons/web/static/src/legacy/js/core/py_utils.js b/addons/web/static/src/legacy/js/core/py_utils.js
index b84a953e492..ca61fd7ed35 100644
--- a/addons/web/static/src/legacy/js/core/py_utils.js
+++ b/addons/web/static/src/legacy/js/core/py_utils.js
@@ -109,8 +109,8 @@ function eval_contexts(contexts, evaluation_context) {
return _(contexts).reduce(function (result_context, ctx) {
// __eval_context evaluations can lead to some of `contexts`'s
// values being null, skip them as well as empty contexts
- if (_.isEmpty(ctx)) { return result_context; }
- if (_.isString(ctx)) {
+ if (Object.keys(ctx || {}).length === 0) { return result_context; }
+ if (typeof ctx === "string") {
// wrap raw strings in context
ctx = { __ref: 'context', __debug: ctx };
}
@@ -144,7 +144,7 @@ function eval_domains(domains, evaluation_context) {
(domains[0][0] === "|" || domains[0][0] === "!")
);
_(domains).each(function (domain) {
- if (_.isString(domain)) {
+ if (typeof domain === "string") {
// wrap raw strings in domain
domain = { __ref: 'domain', __debug: domain };
}
@@ -190,7 +190,7 @@ function eval_groupbys(contexts, evaluation_context) {
evaluation_context = Object.assign(pycontext(), evaluation_context || {});
var result_group = [];
_(contexts).each(function (ctx) {
- if (_.isString(ctx)) {
+ if (typeof ctx === "string") {
// wrap raw strings in context
ctx = { __ref: 'context', __debug: ctx };
}
diff --git a/addons/web/static/src/legacy/js/core/rpc.js b/addons/web/static/src/legacy/js/core/rpc.js
index 68fe98369ab..a36d325a2a3 100644
--- a/addons/web/static/src/legacy/js/core/rpc.js
+++ b/addons/web/static/src/legacy/js/core/rpc.js
@@ -116,9 +116,9 @@ const rpc = {
* @returns {string}
*/
_serializeSort: function (orderBy) {
- return _.map(orderBy, function (order) {
- return order.name + (order.asc !== false ? ' ASC' : ' DESC');
- }).join(', ');
+ return orderBy
+ .map((order) => order.name + (order.asc !== false ? " ASC" : " DESC"))
+ .join(", ");
},
};
diff --git a/addons/web/static/src/legacy/js/core/session.js b/addons/web/static/src/legacy/js/core/session.js
index 85f13a8de92..c7cf10291b2 100644
--- a/addons/web/static/src/legacy/js/core/session.js
+++ b/addons/web/static/src/legacy/js/core/session.js
@@ -47,7 +47,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
// must be able to customize server
var window_origin = location.protocol + "//" + location.host;
origin = origin ? origin.replace( /\/+$/, '') : window_origin;
- if (!_.isUndefined(this.origin) && this.origin !== origin)
+ if (typeof this.origin !== "undefined" && this.origin !== origin)
throw new Error('Session already bound to ' + this.origin);
else
this.origin = origin;
@@ -256,7 +256,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
}
// TODO: remove
- if (! _.isString(url)) {
+ if (typeof url !== "string") {
Object.assign(options, url);
url = url.url;
}
diff --git a/addons/web/static/src/legacy/js/core/utils.js b/addons/web/static/src/legacy/js/core/utils.js
index 5ca513bf99c..0759eb8d07e 100644
--- a/addons/web/static/src/legacy/js/core/utils.js
+++ b/addons/web/static/src/legacy/js/core/utils.js
@@ -949,7 +949,7 @@ const utils = {
*/
toBoolElse: function (str, elseValues, trueValues, falseValues) {
var ret = _.str.toBool(str, trueValues, falseValues);
- if (_.isUndefined(ret)) {
+ if (typeof ret === "undefined") {
return elseValues;
}
return ret;
diff --git a/addons/web/static/src/legacy/js/core/widget.js b/addons/web/static/src/legacy/js/core/widget.js
index d0249a34c54..8306349450b 100644
--- a/addons/web/static/src/legacy/js/core/widget.js
+++ b/addons/web/static/src/legacy/js/core/widget.js
@@ -218,7 +218,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* @param {boolean} [display] use true to show the widget or false to hide it
*/
do_toggle: function (display) {
- if (_.isBoolean(display)) {
+ if (typeof display === "boolean") {
display ? this.do_show() : this.do_hide();
} else if (this.$el) {
this.$el.hasClass('o_hidden') ? this.do_show() : this.do_hide();
@@ -283,9 +283,9 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* @returns {Promise}
*/
replace: function (target) {
- return this._widgetRenderAndInsert(_.bind(function (t) {
+ return this._widgetRenderAndInsert((t) => {
this.$el.replaceAll(t);
- }, this), target);
+ }, target);
},
/**
* Re-sets the widget's root element (el/$el/$el).
@@ -337,7 +337,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
*/
_delegateEvents: function () {
var events = this.events;
- if (_.isEmpty(events)) { return; }
+ if (Object.keys(events || {}).length === 0) { return; }
for(var key in events) {
if (!events.hasOwnProperty(key)) { continue; }
@@ -372,7 +372,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
attrs['class'] = this.className;
}
var $el = $(document.createElement(this.tagName));
- if (!_.isEmpty(attrs)) {
+ if (Object.keys(attrs || {}).length > 0) {
$el.attr(attrs);
}
return $el;
diff --git a/addons/web/static/src/legacy/js/fields/basic_fields.js b/addons/web/static/src/legacy/js/fields/basic_fields.js
index b34f63be877..a4ff266bb8d 100644
--- a/addons/web/static/src/legacy/js/fields/basic_fields.js
+++ b/addons/web/static/src/legacy/js/fields/basic_fields.js
@@ -16,6 +16,7 @@ import session from "web.session";
import field_utils from "web.field_utils";
import utils from "web.utils";
import { sprintf } from "@web/core/utils/strings";
+import { debounce } from "@web/core/utils/timing";
var _t = core._t;
var _lt = core._lt;
@@ -238,7 +239,7 @@ var DebouncedField = AbstractField.extend({
this._isDirty = false;
if (this.mode === 'edit') {
if (this.DEBOUNCE) {
- this._doDebouncedAction = _.debounce(this._doAction, this.DEBOUNCE);
+ this._doDebouncedAction = debounce(this._doAction, this.DEBOUNCE);
} else {
this._doDebouncedAction = this._doAction;
}
diff --git a/addons/web/static/src/legacy/js/fields/field_utils.js b/addons/web/static/src/legacy/js/fields/field_utils.js
index 0c6ef19bb93..32ce9b2f131 100644
--- a/addons/web/static/src/legacy/js/fields/field_utils.js
+++ b/addons/web/static/src/legacy/js/fields/field_utils.js
@@ -14,7 +14,7 @@
* conversions.
*/
-import { sprintf } from "@web/core/utils/strings";
+import { escape, escapeRegExp, sprintf } from "@web/core/utils/strings";
import core from "web.core";
import dom from "web.dom";
import session from "web.session";
@@ -87,10 +87,10 @@ function formatBoolean(value, field, options) {
function formatChar(value, field, options) {
value = typeof value === 'string' ? value : '';
if (options && options.isPassword) {
- return _.str.repeat('*', value ? value.length : 0);
+ return "*".repeat(value ? value.length : 0);
}
if (options && options.escape) {
- value = _.escape(value);
+ value = escape(value);
}
return value;
}
@@ -241,7 +241,7 @@ function formatFloatTime(value, field, options) {
function formatInteger(value, field, options) {
options = options || {};
if (options.isPassword) {
- return _.str.repeat('*', String(value).length);
+ return "*".repeat(String(value).length);
}
if (!value && value !== 0) {
// previously, it returned 'false'. I don't know why. But for the Pivot
@@ -282,8 +282,8 @@ function formatMany2one(value, field, options) {
value = value.data.display_name || '';
}
}
- if (options && options.escape) {
- value = _.escape(value);
+ if (options?.escape) {
+ value = escape(value);
}
return value;
}
@@ -373,7 +373,7 @@ function formatMonetary(value, field, options) {
if (options.forceString) {
return val.join(' ');
}
- return utils.Markup(val.map((v) => _.escape(v)).join(NBSP));
+ return utils.Markup(val.map((v) => escape(v)).join(NBSP));
}
/**
* Returns a string representing the given value (multiplied by 100)
@@ -408,8 +408,8 @@ function formatSelection(value, field, options) {
return '';
}
value = val[1];
- if (options && options.escape) {
- value = _.escape(value);
+ if (options?.escape) {
+ value = escape(value);
}
return value;
}
@@ -575,8 +575,9 @@ function parseDateTime(value, field, options) {
* @returns {float|NaN} the number value contained in the string representation
*/
function parseNumber(value) {
+
if (core._t.database.parameters.thousands_sep) {
- var escapedSep = _.str.escapeRegExp(core._t.database.parameters.thousands_sep);
+ var escapedSep = escapeRegExp(core._t.database.parameters.thousands_sep);
value = value.replace(new RegExp(escapedSep, 'g'), '');
}
if (core._t.database.parameters.decimal_point) {
@@ -735,7 +736,7 @@ function parseMany2one(value) {
display_name: value[1],
};
}
- if (Number.isFinite(value) || _.isString(value)) {
+ if (Number.isFinite(value) || typeof value === "string") {
return {
id: parseInt(value, 10),
};
@@ -753,7 +754,7 @@ export default {
float: formatFloat,
float_factor: formatFloatFactor,
float_time: formatFloatTime,
- html: _.identity, // todo
+ html: (value) => value, // todo
integer: formatInteger,
many2many: formatX2Many,
many2one: formatMany2one,
@@ -767,25 +768,25 @@ export default {
json: formatJson,
},
parse: {
- binary: _.identity,
- boolean: _.identity, // todo
- char: _.identity, // todo
+ binary: (value) => value,
+ boolean: (value) => value, // todo
+ char: (value) => value, // todo
date: parseDate, // todo
datetime: parseDateTime, // todo
float: parseFloat,
float_factor: parseFloatFactor,
float_time: parseFloatTime,
- html: _.identity, // todo
+ html: (value) => value, // todo
integer: parseInteger,
- many2many: _.identity, // todo
+ many2many: (value) => value, // todo
many2one: parseMany2one,
many2one_reference: parseInteger,
monetary: parseMonetary,
- one2many: _.identity,
+ one2many: (value) => value,
percentage: parsePercentage,
reference: parseMany2one,
- selection: _.identity, // todo
- text: _.identity, // todo
- json: _.identity, // todo
+ selection: (value) => value, // todo
+ text: (value) => value, // todo
+ json: (value) => value, // todo
},
};
diff --git a/addons/web/static/src/legacy/js/fields/relational_fields.js b/addons/web/static/src/legacy/js/fields/relational_fields.js
index 0c7fa920777..9d11189800f 100644
--- a/addons/web/static/src/legacy/js/fields/relational_fields.js
+++ b/addons/web/static/src/legacy/js/fields/relational_fields.js
@@ -582,7 +582,7 @@ var FieldMany2One = AbstractField.extend({
* @returns {string} escaped html of value lines
*/
_renderValueLines: function (needFirstLine) {
- const escapedValue = _.escape((this.m2o_value || "").trim());
+ const escapedValue = escape((this.m2o_value || "").trim());
const lines = escapedValue.split('\n');
if (!needFirstLine) {
lines.shift();
@@ -1050,7 +1050,7 @@ var FieldMany2ManyTags = AbstractField.extend({
* @returns {Promise}
*/
_addTag: function (data) {
- if (!_.contains(this.value.res_ids, data.id)) {
+ if (!this.value.res_ids.includes(data.id)) {
return this._setValue({
operation: 'ADD_M2M',
ids: data
@@ -1076,7 +1076,7 @@ var FieldMany2ManyTags = AbstractField.extend({
* @returns {Object}
*/
_getRenderTagsContext: function () {
- var elements = this.value ? _.pluck(this.value.data, 'data') : [];
+ var elements = this.value ? this.value.data.map((d) => d.data) : [];
return {
colorField: this.colorField,
elements: elements,
@@ -1249,7 +1249,7 @@ const M2MAvatarMixin = {
html: true,
placement: 'auto',
content: () => {
- const elements = this.value ? _.pluck(this.value.data, 'data') : [];
+ const elements = this.value ? this.value.data.map((d) => d.data) : [];
return qweb.render('Many2ManyTagAvatarPopover', {
elements: elements.slice(this.visibleAvatarCount - 1),
});
diff --git a/addons/web/static/src/legacy/js/public/public_root.js b/addons/web/static/src/legacy/js/public/public_root.js
index ccbef75e350..5f77cbe6e58 100644
--- a/addons/web/static/src/legacy/js/public/public_root.js
+++ b/addons/web/static/src/legacy/js/public/public_root.js
@@ -251,7 +251,7 @@ export const PublicRoot = publicWidget.RootWidget.extend({
args = args.concat(ev.target);
var route = args[0];
- if (_.str.startsWith(route, '/web/dataset/call_kw/')) {
+ if (String(route).startsWith("/web/dataset/call_kw/")) {
var params = args[1];
var options = args[2];
var noContextKeys;
diff --git a/addons/web/static/src/legacy/js/public/public_widget.js b/addons/web/static/src/legacy/js/public/public_widget.js
index 88b7943a1a2..4aefaa8793e 100644
--- a/addons/web/static/src/legacy/js/public/public_widget.js
+++ b/addons/web/static/src/legacy/js/public/public_widget.js
@@ -212,14 +212,14 @@ var PublicWidget = Widget.extend({
}
// If the method has no meaningful options, use the default Widget
// system
- var isAsync = _.contains(methodOptions, 'async');
+ var isAsync = methodOptions.includes('async');
if (!isAsync) {
events[event] = method;
return;
}
method = self.proxy(methodOptions[methodOptions.length - 1]);
- if (_.str.startsWith(event, 'click')) {
+ if (String(event).startsWith("click")) {
// Protect click handler to be called multiple times by
// mistake by the user and add a visual disabling effect
// for buttons.
diff --git a/addons/web/static/src/legacy/js/services/config.js b/addons/web/static/src/legacy/js/services/config.js
index d07ba6c9d2e..9d7c3d1bd52 100644
--- a/addons/web/static/src/legacy/js/services/config.js
+++ b/addons/web/static/src/legacy/js/services/config.js
@@ -114,7 +114,10 @@ function _updateSizeProps() {
}
}
-_.invoke(medias, 'addListener', _updateSizeProps);
+medias.forEach((media) => {
+ media.addListener(_updateSizeProps);
+});
+
_updateSizeProps();
export default config;
diff --git a/addons/web/static/src/legacy/js/views/basic/basic_model.js b/addons/web/static/src/legacy/js/views/basic/basic_model.js
index f7d197bcb15..5210fb17693 100644
--- a/addons/web/static/src/legacy/js/views/basic/basic_model.js
+++ b/addons/web/static/src/legacy/js/views/basic/basic_model.js
@@ -366,7 +366,7 @@ var BasicModel = AbstractModel.extend({
return this._rpc({
model: modelName,
method: 'unlink',
- args: [_.pluck(records, 'res_id')],
+ args: [records.map(r => r.res_id)],
context: context,
})
.then(function () {
@@ -908,7 +908,7 @@ var BasicModel = AbstractModel.extend({
// We only need to load the datapoint in the first case.
if (field.value && field.value.length) {
if (_.isObject(field.value[0])) {
- dpParams.res_ids = _.pluck(field.value, 'id');
+ dpParams.res_ids = field.value.map((v) => v.id);
dataPoint = self._makeDataPoint(dpParams);
field.value.forEach((data) => {
var recordDP = self._makeDataPoint({
@@ -1298,7 +1298,7 @@ var BasicModel = AbstractModel.extend({
args: [resIDs],
})
.then(function (action) {
- if (!_.isEmpty(action)) {
+ if (Object.keys(action || {}).length > 0) {
return new Promise(function (resolve, reject) {
self.do_action(action, {
on_close: function (result) {
@@ -1339,7 +1339,7 @@ var BasicModel = AbstractModel.extend({
})
.then(function (action) {
// optionally clear the DataManager's cache
- if (!_.isEmpty(action)) {
+ if (Object.keys(action || {}).length > 0) {
return new Promise(function (resolve, reject) {
self.do_action(action, {
on_close: function () {
@@ -2013,7 +2013,7 @@ var BasicModel = AbstractModel.extend({
def = this._rpc({
model: list.model,
method: 'read',
- args: [_.pluck(data, 'id'), fieldNames],
+ args: [data.map((d) => d.id), fieldNames],
context: Object.assign({}, record.context, field.context, list.getContext()),
}).then(function (records) {
records.forEach((record) => {
@@ -2895,7 +2895,7 @@ var BasicModel = AbstractModel.extend({
*/
_fetchSpecialRelation: function (record, fieldName) {
var field = record.fields[fieldName];
- if (!_.contains(["many2one", "many2many", "one2many"], field.type)) {
+ if (!["many2one", "many2many", "one2many"].includes(field.type)) {
return Promise.resolve();
}
@@ -3318,7 +3318,7 @@ var BasicModel = AbstractModel.extend({
this._sortList(list);
if (type === 'many2many' || list._forceM2MLink) {
var relRecordCreated = relRecordAdded.filter(rec => typeof rec.res_id === 'string');
- var realIDs = _.difference(list.res_ids, _.pluck(relRecordCreated, 'res_id'));
+ var realIDs = _.difference(list.res_ids, relRecordCreated.map(r => r.res_id));
// deliberately generate a single 'replace' command instead
// of a 'delete' and a 'link' commands with the exact diff
// because 1) performance-wise it doesn't change anything
@@ -3333,7 +3333,7 @@ var BasicModel = AbstractModel.extend({
// updated (it may happen with editable lists)
relRecordUpdated.forEach((relRecord) => {
var changes = self._generateChanges(relRecord, options);
- if (!_.isEmpty(changes)) {
+ if (Object.keys(changes || {}).length > 0) {
var command = x2ManyCommands.update(relRecord.res_id, changes);
commands[fieldName].push(command);
}
@@ -3348,18 +3348,18 @@ var BasicModel = AbstractModel.extend({
var didChange = false;
var changes, command, relRecord;
for (var i = 0; i < list.res_ids.length; i++) {
- if (_.contains(keptIds, list.res_ids[i])) {
+ if (keptIds.includes(list.res_ids[i])) {
// this is an id that already existed
relRecord = _.findWhere(relRecordUpdated, {res_id: list.res_ids[i]});
changes = relRecord ? this._generateChanges(relRecord, options) : {};
- if (!_.isEmpty(changes)) {
+ if (Object.keys(changes || {}).length > 0) {
command = x2ManyCommands.update(relRecord.res_id, changes);
didChange = true;
} else {
command = x2ManyCommands.link_to(list.res_ids[i]);
}
commands[fieldName].push(command);
- } else if (_.contains(addedIds, list.res_ids[i])) {
+ } else if (addedIds.includes(list.res_ids[i])) {
// this is a new id (maybe existing in DB, but new in JS)
relRecord = _.findWhere(relRecordAdded, {res_id: list.res_ids[i]});
if (!relRecord) {
@@ -3443,7 +3443,7 @@ var BasicModel = AbstractModel.extend({
if (options.full || !(options.fieldName || options.additionalContext)) {
var context_to_add = options.sanitize_default_values ?
_.omit(element.context, function (val, key) {
- return _.str.startsWith(key, 'default_');
+ return String(key).startsWith('default_');
})
: element.context;
context.add(context_to_add);
@@ -4643,7 +4643,7 @@ var BasicModel = AbstractModel.extend({
});
r._changes[fieldName] = rec.id;
many2ones[fieldName] = true;
- } else if (_.contains(['one2many', 'many2many'], fieldType)) {
+ } else if (['one2many', 'many2many'].includes(fieldType)) {
var x2mCommands = value[2][fieldName];
defs.push(self._processX2ManyCommands(r, fieldName, x2mCommands));
} else {
@@ -4799,7 +4799,7 @@ var BasicModel = AbstractModel.extend({
groups.forEach((group) => {
var aggregateValues = {};
for (const [key, value] of Object.entries(group)) {
- if (_.contains(fields, key) && key !== groupByField &&
+ if (fields.includes(key) && key !== groupByField &&
AGGREGATABLE_TYPES.includes(list.fields[key].type)) {
aggregateValues[key] = value;
}
@@ -4870,7 +4870,7 @@ var BasicModel = AbstractModel.extend({
// readGroup but are not there anymore.
// Note that these groups are put after existing groups so
// the order is not conserved. A sort *might* be useful.
- var emptyGroupsIDs = _.difference(_.pluck(previousGroups, 'id'), list.data);
+ var emptyGroupsIDs = _.difference(previousGroups.map((group) => group.id), list.data);
emptyGroupsIDs.forEach((groupID) => {
list.data.push(groupID);
var emptyGroup = self.localData[groupID];
@@ -4884,9 +4884,9 @@ var BasicModel = AbstractModel.extend({
if (!options.onlyGroups) {
// generate the res_ids of the main list, being the concatenation
// of the fetched res_ids in each group
- list.res_ids = _.flatten(_.map(groups, function (group) {
+ list.res_ids = _.map(groups, function (group) {
return group ? group.res_ids : [];
- }));
+ }).flat();
}
return list;
}).then(function () {
@@ -4918,7 +4918,7 @@ var BasicModel = AbstractModel.extend({
// order field first to apply the order on all pages
if (list.res_ids.length > list.limit && list.orderedBy.length) {
if (!list.orderedResIDs) {
- var fieldNames = _.pluck(list.orderedBy, 'name');
+ var fieldNames = list.orderedBy.map((list) => list.name);
def = this._readMissingFields(list, list.res_ids.filter(id => Number.isFinite(id)), fieldNames, options.withoutRecordData);
}
def.then(function () {
@@ -5077,7 +5077,7 @@ var BasicModel = AbstractModel.extend({
* @returns {boolean} false if the value was already the given one
*/
_saveSpecialDataCache: function (record, fieldName, value) {
- if (_.isEqual(record._specialDataCache[fieldName], value)) {
+ if (JSON.stringify(record._specialDataCache[fieldName]) === JSON.stringify(value)) {
return false;
}
record._specialDataCache[fieldName] = value;
@@ -5121,7 +5121,7 @@ var BasicModel = AbstractModel.extend({
return prom.then(function (result) {
delete list.__data;
list.count = result.length;
- var ids = _.pluck(result.records, 'id');
+ var ids = result.records.map((r) => r.id);
var data = _.map(result.records, function (record) {
var dataPoint = self._makeDataPoint({
context: list.context,
@@ -5291,9 +5291,9 @@ var BasicModel = AbstractModel.extend({
var self = this;
if (element.parentID) {
var parent = this.localData[element.parentID];
- parent.res_ids = _.flatten(_.map(parent.data, function (dataPointID) {
+ parent.res_ids = _.map(parent.data, function (dataPointID) {
return self.localData[dataPointID].res_ids;
- }));
+ }).flat();
this._updateParentResIDs(parent);
}
},
@@ -5395,7 +5395,7 @@ var BasicModel = AbstractModel.extend({
if (!field) {
continue;
}
- if (_.contains(['one2many', 'many2one', 'many2many'], field.type)) {
+ if (['one2many', 'many2one', 'many2many'].includes(field.type)) {
var hasChange = element._changes && fieldName in element._changes;
var value = hasChange ? element._changes[fieldName] : element.data[fieldName];
var relationalElement = this.localData[value];
diff --git a/addons/web/static/src/legacy/js/views/standalone_field_manager_mixin.js b/addons/web/static/src/legacy/js/views/standalone_field_manager_mixin.js
index b2f71e46912..cef759851c4 100644
--- a/addons/web/static/src/legacy/js/views/standalone_field_manager_mixin.js
+++ b/addons/web/static/src/legacy/js/views/standalone_field_manager_mixin.js
@@ -42,7 +42,7 @@ var StandaloneFieldManagerMixin = Object.assign({}, FieldManagerMixin, {
var result = FieldManagerMixin._confirmChange.apply(this, arguments);
var record = this.model.get(id);
for (const [fieldName, widget] of Object.entries(this.registeredWidgets[id])) {
- if (_.contains(fields, fieldName)) {
+ if (fields.includes(fieldName)) {
widget.reset(record, event);
}
}
diff --git a/addons/web/static/src/legacy/js/views/view_utils.js b/addons/web/static/src/legacy/js/views/view_utils.js
index b1a3b734330..d1bcdc38320 100644
--- a/addons/web/static/src/legacy/js/views/view_utils.js
+++ b/addons/web/static/src/legacy/js/views/view_utils.js
@@ -62,12 +62,12 @@ var viewUtils = {
}
var dateTypes = ['date', 'datetime'];
if (!list.fields[groupByField].readonly &&
- _.contains(dateTypes, list.fields[groupByField].type)) {
+ dateTypes.includes(list.fields[groupByField].type)) {
return list.fieldsInfo && list.fieldsInfo[list.viewType][groupByField] &&
list.fieldsInfo[list.viewType][groupByField].allowGroupRangeValue;
}
var availableTypes = ['char', 'boolean', 'many2one', 'selection'];
- return _.contains(availableTypes, list.fields[groupByField].type);
+ return availableTypes.includes(list.fields[groupByField].type);
},
/**
* @param {string} arch view arch
diff --git a/addons/web/static/src/legacy/js/widgets/domain_selector.js b/addons/web/static/src/legacy/js/widgets/domain_selector.js
index 538992fde31..6b5feab4e32 100644
--- a/addons/web/static/src/legacy/js/widgets/domain_selector.js
+++ b/addons/web/static/src/legacy/js/widgets/domain_selector.js
@@ -694,7 +694,7 @@ var DomainLeaf = DomainNode.extend({
// Set list of operators according to field type
var selectedField = this.fieldSelector.getSelectedField() || {};
this.operators = this._getOperatorsFromType(selectedField.type);
- if (_.contains(["child_of", "parent_of", "like", "not like", "=like", "=ilike"], this.operator)) {
+ if (["child_of", "parent_of", "like", "not like", "=like", "=ilike"].includes(this.operator)) {
// In case user entered manually or from demo data
this.operators[this.operator] = operator_mapping[this.operator];
} else if (!this.operators[this.operator]) {
@@ -726,7 +726,7 @@ var DomainLeaf = DomainNode.extend({
}
// TODO the value could be a m2o input, etc...
- if (_.contains(["date", "datetime"], selectedField.type)) {
+ if (["date", "datetime"].includes(selectedField.type)) {
this.valueWidget = new (selectedField.type === "datetime" ? datepicker.DateTimeWidget : datepicker.DateWidget)(this);
wDefs.push(this.valueWidget.appendTo("").then((function () {
this.valueWidget.$el.addClass("o_domain_leaf_value_input");
@@ -821,10 +821,10 @@ var DomainLeaf = DomainNode.extend({
_changeOperator: function (operator, silent) {
this.operator = operator;
- if (_.contains(["set", "not set"], this.operator)) {
+ if (["set", "not set"].includes(this.operator)) {
this.operator = this.operator === "not set" ? "=" : "!=";
this.value = false;
- } else if (_.contains(["in", "not in"], this.operator)) {
+ } else if (["in", "not in"].includes(this.operator)) {
this.value = Array.isArray(this.value) ? this.value : this.value ? ("" + this.value).split(",") : [];
} else {
if (Array.isArray(this.value)) {
@@ -857,22 +857,22 @@ var DomainLeaf = DomainNode.extend({
}
if (selectedField.type === "boolean") {
- if (!_.isBoolean(this.value)) { // Convert boolean-like value to boolean
+ if (typeof this.value !== "boolean") { // Convert boolean-like value to boolean
this.value = !!parseFloat(this.value);
}
} else if (selectedField.type === "selection") {
if (!_.some(selectedField.selection, (function (option) { return option[0] === this.value; }).bind(this))) {
this.value = selectedField.selection[0][0];
}
- } else if (_.contains(["date", "datetime"], selectedField.type)) {
- if (couldNotParse || _.isBoolean(this.value)) {
+ } else if (["date", "datetime"].includes(selectedField.type)) {
+ if (couldNotParse || typeof this.value === "boolean") {
this.value = field_utils.parse[selectedField.type](field_utils.format[selectedField.type](moment())).toJSON(); // toJSON to get date with server format
} else {
this.value = this.value.toJSON(); // toJSON to get date with server format
}
} else {
// Never display "true" or "false" strings from boolean value
- if (_.isBoolean(this.value)) {
+ if (typeof this.value === "boolean") {
this.value = "";
} else if (_.isObject(this.value) && !Array.isArray(this.value)) { // Can be object if parsed to x2x representation
this.value = this.value.id || value || "";
@@ -975,7 +975,7 @@ var DomainLeaf = DomainNode.extend({
// TODO The two following functions should be in an independant widget
on_add_tag: function (e) {
if (e.type === "keyup" && e.which !== $.ui.keyCode.ENTER) return;
- if (!_.contains(["not in", "in"], this.operator)) return;
+ if (!["not in", "in"].includes(this.operator)) return;
var values = Array.isArray(this.value) ? this.value.slice() : [];
diff --git a/addons/web/static/src/legacy/js/widgets/model_field_selector_popover.js b/addons/web/static/src/legacy/js/widgets/model_field_selector_popover.js
index 85c0a77a170..5019229fd41 100644
--- a/addons/web/static/src/legacy/js/widgets/model_field_selector_popover.js
+++ b/addons/web/static/src/legacy/js/widgets/model_field_selector_popover.js
@@ -158,7 +158,7 @@ var ModelFieldSelectorPopover = Widget.extend({
* @returns {Promise} resolved once the re-rendering is finished
*/
setChain: function (chain) {
- if (_.isEqual(chain, this.chain)) {
+ if (JSON.stringify(chain) === JSON.stringify(this.chain)) {
return Promise.resolve();
}
@@ -284,7 +284,7 @@ var ModelFieldSelectorPopover = Widget.extend({
* @param {Object} field - the field to add to the chain node
*/
_goToNextPage: function (field) {
- if (!_.isEqual(this._getLastPageField(field.name), field)) return;
+ if (JSON.stringify(this._getLastPageField(field.name)) !== JSON.stringify(field)) return;
this._validate(true);
this._addChainNode(field.name);
@@ -489,7 +489,7 @@ var ModelFieldSelectorPopover = Widget.extend({
* @param {Object} field - the field to select
*/
_selectField: function (field) {
- if (!_.isEqual(this._getLastPageField(field.name), field)) return;
+ if (JSON.stringify(this._getLastPageField(field.name)) !== JSON.stringify(field)) return;
this._validate(true);
this._addChainNode(field.name);
diff --git a/addons/web/static/src/legacy/js/widgets/name_and_signature.js b/addons/web/static/src/legacy/js/widgets/name_and_signature.js
index a7a97ea6088..ce3a14a4e36 100644
--- a/addons/web/static/src/legacy/js/widgets/name_and_signature.js
+++ b/addons/web/static/src/legacy/js/widgets/name_and_signature.js
@@ -4,6 +4,7 @@ import core from "web.core";
import config from "web.config";
import utils from "web.utils";
import Widget from "web.Widget";
+import { debounce } from "@web/core/utils/timing";
/**
@@ -147,7 +148,7 @@ var NameAndSignature = Widget.extend({
}
// Resize the signature area if it is resized
- $(window).on('resize.o_web_sign_name_and_signature', _.debounce(function () {
+ $(window).on('resize.o_web_sign_name_and_signature', debounce(function () {
if (self.isDestroyed()) {
// May happen since this is debounced
return;
diff --git a/addons/web/static/tests/core/utils/numbers_test.js b/addons/web/static/tests/core/utils/numbers_test.js
new file mode 100644
index 00000000000..6c43f4b680f
--- /dev/null
+++ b/addons/web/static/tests/core/utils/numbers_test.js
@@ -0,0 +1,15 @@
+/** @odoo-module **/
+
+import { range } from "@web/core/utils/numbers";
+
+QUnit.module("utils", () => {
+ QUnit.module("Numbers", () => {
+ QUnit.test("test range function from core/utils/numbers.js", (assert) => {
+ assert.deepEqual(range(0, 10), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ assert.deepEqual(range(0, -10, -1), [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]);
+ assert.deepEqual(range(0, 35, 5), [0, 5, 10, 15, 20, 25, 30]);
+ assert.deepEqual(range(-10, 6, 2), [-10, -8, -6, -4, -2, 0, 2, 4]);
+ assert.deepEqual(range(4, -4, -1), [4, 3, 2, 1, 0, -1, -2, -3]);
+ });
+ });
+});
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 32a160d98af..25401329923 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -544,7 +544,8 @@ export class MockServer {
item.includes("readonly")
))) &&
(!["1", "True"].includes(node.getAttribute("readonly")) ||
- !_.isEmpty(evaluateExpr(node.getAttribute("attrs") || "{}")))
+ Object.keys(evaluateExpr(node.getAttribute("attrs") || "{}") || {}).length >
+ 0)
);
}
default:
@@ -1275,7 +1276,7 @@ export class MockServer {
group.__domain = [[fieldName, "=", value]].concat(group.__domain);
}
}
- if (_.isEmpty(group.__range)) {
+ if (Object.keys(group.__range || {}).length === 0) {
delete group.__range;
}
// compute count key to match dumb server logic...
diff --git a/addons/web/static/tests/legacy/control_panel/filter_menu_tests.js b/addons/web/static/tests/legacy/control_panel/filter_menu_tests.js
index 1b674d7ad6f..e6a2a267cac 100644
--- a/addons/web/static/tests/legacy/control_panel/filter_menu_tests.js
+++ b/addons/web/static/tests/legacy/control_panel/filter_menu_tests.js
@@ -5,6 +5,7 @@
import testUtils from "web.test_utils";
import * as cpHelpers from "@web/../tests/search/helpers";
+
const { createControlPanel, mock } = testUtils;
const { patchDate } = mock;
diff --git a/addons/web/static/tests/legacy/core/time_tests.js b/addons/web/static/tests/legacy/core/time_tests.js
index c04afb3c430..e4b5bb6ed75 100644
--- a/addons/web/static/tests/legacy/core/time_tests.js
+++ b/addons/web/static/tests/legacy/core/time_tests.js
@@ -40,7 +40,7 @@ QUnit.module('core', {}, function () {
try {
window.Date = function (v) {
- if (_.isUndefined(v)) {
+ if (typeof v === "undefined") {
v = '2013-10-31 12:34:56';
}
return new wDate(v);
@@ -73,7 +73,7 @@ QUnit.module('core', {}, function () {
try {
window.Date = function (v) {
- if (_.isUndefined(v)) {
+ if (typeof v === "undefined") {
v = '2013-10-31 12:34:56';
}
return new wDate(v);
diff --git a/addons/web/static/tests/legacy/core/widget_tests.js b/addons/web/static/tests/legacy/core/widget_tests.js
index ff72083602b..b14f608d987 100644
--- a/addons/web/static/tests/legacy/core/widget_tests.js
+++ b/addons/web/static/tests/legacy/core/widget_tests.js
@@ -100,7 +100,7 @@ QUnit.module('core', {}, function () {
assert.strictEqual(widget.el.nodeName, 'DIV', "should have generated the default element");
assert.strictEqual(widget.el.attributes.length, 0, "should not have generated any attribute");
- assert.ok(_.isEmpty(widget.$el.html(), "should not have generated any content"));
+ assert.ok(Object.keys(widget.$el.html() || {}).length === 0, "should not have generated any content");
widget.destroy();
});
diff --git a/addons/web/static/tests/legacy/helpers/mock_server.js b/addons/web/static/tests/legacy/helpers/mock_server.js
index b5cbe83299a..e33c5045455 100644
--- a/addons/web/static/tests/legacy/helpers/mock_server.js
+++ b/addons/web/static/tests/legacy/helpers/mock_server.js
@@ -216,7 +216,7 @@ var MockServer = Class.extend({
if ('default' in model.fields[fieldName]) {
const def = model.fields[fieldName].default;
record[fieldName] = typeof def === 'function' ? def.call(this) : def;
- } else if (_.contains(['one2many', 'many2many'], model.fields[fieldName].type)) {
+ } else if (["one2many", "many2many"].includes(model.fields[fieldName].type)) {
record[fieldName] = [];
} else {
record[fieldName] = false;
@@ -1254,7 +1254,7 @@ var MockServer = Class.extend({
ids = [ids];
}
var records = this.data[model].records;
- var names = _.map(ids, function (id) {
+ var names = ids.map(id => {
return id ? [id, _.findWhere(records, {id: id}).display_name] : [null, ""];
});
return names;
@@ -1299,7 +1299,7 @@ var MockServer = Class.extend({
if (str.length) {
records = records.filter(record => record.display_name.indexOf(str) !== -1);
}
- var result = _.map(records, function (record) {
+ var result = records.map(record => {
return [record.id, record.display_name];
});
return result.slice(0, limit);
@@ -1471,8 +1471,8 @@ var MockServer = Class.extend({
});
// filter out non existing fields
- aggregatedFields = aggregatedFields.filter(name => {
- return name in self.data[model].fields && !(_.contains(groupByFieldNames,name));
+ aggregatedFields = aggregatedFields.filter((name) => {
+ return name in self.data[model].fields && !groupByFieldNames.includes(name);
});
function aggregateFields(group, records) {
@@ -1487,7 +1487,7 @@ var MockServer = Class.extend({
}
}
if (type === 'many2one') {
- var ids = _.pluck(records, aggregatedFields[i]);
+ var ids = records.map((record) => aggregatedFields[i]);
group[aggregatedFields[i]] = _.uniq(ids).length || null;
}
}
@@ -1657,7 +1657,7 @@ var MockServer = Class.extend({
group.__domain = [[fieldName, "=", value]].concat(group.__domain);
}
}
- if (_.isEmpty(group.__range)) {
+ if (Object.keys(group.__range || {}).length === 0) {
delete group.__range;
}
// compute count key to match dumb server logic...
@@ -1869,7 +1869,9 @@ var MockServer = Class.extend({
if (!Array.isArray(ids)) {
ids = [ids];
}
- this.data[model].records = this.data[model].records.filter(record => !_.contains(ids, record.id));
+ this.data[model].records = this.data[model].records.filter(
+ (record) => !ids.includes(record.id)
+ );
// update value of relationnal fields pointing to the deleted records
Object.values(this.data).forEach((d) => {
@@ -1995,7 +1997,10 @@ var MockServer = Class.extend({
case '/web/dataset/resequence':
return this._mockResequence(args);
}
- if (route.indexOf('/web/image') >= 0 || _.contains(['.png', '.jpg'], route.substr(route.length - 4))) {
+ if (
+ route.indexOf("/web/image") >= 0 ||
+ [".png", ".jpg"].includes(route.substr(route.length - 4))
+ ) {
return;
}
switch (args.method) {
@@ -2204,7 +2209,7 @@ var MockServer = Class.extend({
if (!field) {
throw Error(`Mock: Can't write value "${JSON.stringify(value)}" on field "${field_changed}" on record "${model},${id}" (field is undefined)`);
}
- if (_.contains(['one2many', 'many2many'], field.type)) {
+ if (["one2many", "many2many"].includes(field.type)) {
var ids = _.clone(record[field_changed]) || [];
if (
@@ -2234,7 +2239,7 @@ var MockServer = Class.extend({
} else if (command[0] === 3) { // FORGET
ids = _.without(ids, command[1]);
} else if (command[0] === 4) { // LINK_TO
- if (!_.contains(ids, command[1])) {
+ if (!ids.includes(command[1])) {
ids.push(command[1]);
}
} else if (command[0] === 5) { // DELETE ALL
diff --git a/addons/web/static/tests/legacy/qweb_tests.js b/addons/web/static/tests/legacy/qweb_tests.js
index 044eb613b32..7c3c5cd768d 100644
--- a/addons/web/static/tests/legacy/qweb_tests.js
+++ b/addons/web/static/tests/legacy/qweb_tests.js
@@ -4,6 +4,7 @@ import {Markup} from "web.utils";
var qwebPath = '/web/static/lib/qweb/';
import {hushConsole} from "@web/../tests/helpers/utils";
+import { escape } from "@web/core/utils/strings";
function trim(s) {
return s.replace(/(^\s+|\s+$)/g, '');
@@ -114,13 +115,13 @@ QUnit.module('QWeb', {
QUnit.test('escape', assert => {
// not strictly about qweb...
assert.expect(8);
- assert.equal(_.escape('a'), 'a');
- assert.equal(_.escape(''), '<a>');
+ assert.equal(escape('a'), 'a');
+ assert.equal(escape(''), '<a>');
assert.equal(_.escape({[_.escapeMethod]() { return 'a'; }}), 'a');
assert.equal(_.escape({[_.escapeMethod]() { return ''; }}), '');
- assert.equal(_.escape(Markup('a')), 'a');
+ assert.equal(escape(Markup('a')), 'a');
assert.equal(_.escape(Markup('')), '');
- assert.equal(_.escape(Markup`a`), 'a');
+ assert.equal(escape(Markup`a`), 'a');
assert.equal(_.escape(Markup``), '');
});
QUnit.module('t-out', {}, () => {
diff --git a/addons/web/static/tests/legacy/views/basic_model_tests.js b/addons/web/static/tests/legacy/views/basic_model_tests.js
index 41608d6be1b..0cbfd98d8d8 100644
--- a/addons/web/static/tests/legacy/views/basic_model_tests.js
+++ b/addons/web/static/tests/legacy/views/basic_model_tests.js
@@ -1374,7 +1374,7 @@
await model.load(params).then(function (resultID) {
var record = model.get(resultID);
- assert.ok(_.isEqual(record.data.category.res_ids, [12, 14]),
+ assert.ok(JSON.stringify(record.data.category.res_ids) === JSON.stringify([12, 14]),
"category field should have correct default value");
});
diff --git a/addons/web/static/tests/qunit.js b/addons/web/static/tests/qunit.js
index 28d76587167..4a461b047cf 100644
--- a/addons/web/static/tests/qunit.js
+++ b/addons/web/static/tests/qunit.js
@@ -8,6 +8,7 @@ import {
fullAnnotatedTraceback,
} from "@web/core/errors/error_utils";
import { registry } from "@web/core/registry";
+import { escape } from "@web/core/utils/strings";
function setQUnitDebugMode() {
owl.whenReady(() => document.body.classList.add("debug")); // make the test visible to the naked eye
@@ -263,15 +264,15 @@ export function setupQUnit() {
modulesAlert.classList.toggle("alert-info");
let error = "Some modules couldn't be started:";
if (info.failed.length) {
- const failedList = info.failed.map((mod) => "- " + _.escape(mod) + "
");
+ const failedList = info.failed.map((mod) => "- " + escape(mod) + "
");
error += `- Failed modules:
`;
}
if (info.missing.length) {
- const missingList = info.missing.map((mod) => "- " + _.escape(mod) + "
");
+ const missingList = info.missing.map((mod) => "- " + escape(mod) + "
");
error += `- Missing dependencies:
`;
}
if (info.unloaded.length) {
- const unloadedList = info.unloaded.map((mod) => "- " + _.escape(mod) + "
");
+ const unloadedList = info.unloaded.map((mod) => "- " + escape(mod) + "
");
error += `
- Non loaded modules due to missing dependencies:
diff --git a/addons/web/static/tests/views/fields/many2many_field_tests.js b/addons/web/static/tests/views/fields/many2many_field_tests.js
index 8b66bae6d63..19a2d109c85 100644
--- a/addons/web/static/tests/views/fields/many2many_field_tests.js
+++ b/addons/web/static/tests/views/fields/many2many_field_tests.js
@@ -286,7 +286,8 @@ QUnit.module("Fields", (hooks) => {
});
var ids = _.sortBy([12, 15, 18].concat(createdType.id), _.identity.bind(_));
assert.ok(
- _.isEqual(_.sortBy(commands[0][2], _.identity.bind(_)), ids),
+ JSON.stringify(_.sortBy(commands[0][2], _.identity.bind(_))) ===
+ JSON.stringify(ids),
"new value should be " + ids
);
}
diff --git a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js
index 55c29168685..ca7ab85d064 100644
--- a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js
+++ b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js
@@ -481,7 +481,10 @@ QUnit.module("Fields", (hooks) => {
6,
"generated command should be REPLACE WITH"
);
- assert.ok(_.isEqual(commands[0][2], [12]), "new value should be [12]");
+ assert.ok(
+ JSON.stringify(commands[0][2]) === JSON.stringify([12]),
+ "new value should be [12]"
+ );
}
},
});
diff --git a/addons/web/static/tests/views/fields/percent_pie_field_tests.js b/addons/web/static/tests/views/fields/percent_pie_field_tests.js
index 91cb25074e1..1a8aa489573 100644
--- a/addons/web/static/tests/views/fields/percent_pie_field_tests.js
+++ b/addons/web/static/tests/views/fields/percent_pie_field_tests.js
@@ -93,19 +93,17 @@ QUnit.module("Fields", (hooks) => {
"should have 10% as pie value since int_field=10"
);
assert.ok(
- _.str.include(
+ String(
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_mask").style
- .transform,
- "rotate(180deg)"
- ),
+ .transform
+ ).includes("rotate(180deg)"),
"left mask should be covering the whole left side of the pie"
);
assert.ok(
- _.str.include(
+ String(
target.querySelectorAll(".o_field_percent_pie.o_field_widget .o_pie .o_mask")[1]
- .style.transform,
- "rotate(36deg)"
- ),
+ .style.transform
+ ).includes("rotate(36deg)"),
"right mask should be rotated from 360*(10/100) = 36 degrees"
);
@@ -162,11 +160,10 @@ QUnit.module("Fields", (hooks) => {
"should have 80% as pie value since int_field=80"
);
assert.ok(
- _.str.include(
+ String(
target.querySelector(".o_field_percent_pie.o_field_widget .o_pie .o_mask").style
- .transform,
- "rotate(288deg)"
- ),
+ .transform
+ ).includes("rotate(288deg)"),
"left mask should be rotated from 360*(80/100) = 288 degrees"
);
assert.hasClass(
diff --git a/addons/web_editor/static/src/js/backend/convert_inline.js b/addons/web_editor/static/src/js/backend/convert_inline.js
index 2d11ead61c8..3b93c1dc876 100644
--- a/addons/web_editor/static/src/js/backend/convert_inline.js
+++ b/addons/web_editor/static/src/js/backend/convert_inline.js
@@ -391,7 +391,7 @@ function classToStyle($editable, cssRules) {
style = `${key}:${value};${style}`;
}
};
- if (_.isEmpty(style)) {
+ if (Object.keys(style || {}).length === 0) {
writes.push(() => { node.removeAttribute('style'); });
} else {
writes.push(() => {
@@ -1371,7 +1371,12 @@ function _normalizeStyle(style) {
const normalizedStyle = {};
for (const styleName of style) {
const value = style[styleName];
- if (value && !styleName.includes('animation') && !styleName.includes('-webkit') && _.isString(value)) {
+ if (
+ value &&
+ !styleName.includes("animation") &&
+ !styleName.includes("-webkit") &&
+ typeof value === "string"
+ ) {
const normalizedStyleName = styleName.replace(/-(.)/g, (a, b) => b.toUpperCase());
normalizedStyle[styleName] = style[normalizedStyleName];
if (style.getPropertyPriority(styleName) === 'important') {
diff --git a/addons/web_editor/static/src/js/backend/html_field.js b/addons/web_editor/static/src/js/backend/html_field.js
index 0f002da5bb5..35b0379ad28 100644
--- a/addons/web_editor/static/src/js/backend/html_field.js
+++ b/addons/web_editor/static/src/js/backend/html_field.js
@@ -62,8 +62,9 @@ export class HtmlFieldWysiwygAdapterComponent extends ComponentAdapter {
stripHistoryIds(newValue) !== stripHistoryIds(newProps.editingValue) &&
stripHistoryIds(lastValue) !== stripHistoryIds(newValue)
) ||
- !_.isEqual(lastRecordInfo, newRecordInfo) ||
- !_.isEqual(lastCollaborationChannel, newCollaborationChannel))
+ JSON.stringify(lastRecordInfo) !== JSON.stringify(newRecordInfo) ||
+ JSON.stringify(lastCollaborationChannel) !== JSON.stringify(newCollaborationChannel)
+ )
{
this.widget.resetEditor(newValue, newProps.widgetArgs[0]);
this.env.onWysiwygReset && this.env.onWysiwygReset();
@@ -132,7 +133,7 @@ export class HtmlField extends Component {
res_model: newProps.record.resModel,
res_id: newProps.record.resId,
};
- if (!_.isEqual(this._lastRecordInfo, newRecordInfo)) {
+ if (JSON.stringify(this._lastRecordInfo) !== JSON.stringify(newRecordInfo)) {
this.currentEditingValue = undefined;
}
this._lastRecordInfo = newRecordInfo;
diff --git a/addons/web_editor/static/src/js/common/ace.js b/addons/web_editor/static/src/js/common/ace.js
index ab1583929a7..c69d795de2c 100644
--- a/addons/web_editor/static/src/js/common/ace.js
+++ b/addons/web_editor/static/src/js/common/ace.js
@@ -8,6 +8,7 @@ import Dialog from "web.Dialog";
import Widget from "web.Widget";
import localStorage from "web.local_storage";
import { sprintf } from "@web/core/utils/strings";
+import { debounce } from "@web/core/utils/timing";
var _t = core._t;
@@ -110,10 +111,10 @@ var checkSCSS = (function () {
return function (scss) {
var stack = [];
var line = 1;
- for (var i = 0 ; i < scss.length ; i++) {
- if (_.contains(openings, scss[i])) {
+ for (var i = 0; i < scss.length; i++) {
+ if (openings.includes(scss[i])) {
stack.push(scss[i]);
- } else if (_.contains(closings, scss[i])) {
+ } else if (closings.includes(scss[i])) {
if (stack.pop() !== mapping[scss[i]]) {
return _getCheckReturn(false, line, _t("Unexpected ") + scss[i]);
}
@@ -254,7 +255,7 @@ var ViewEditor = Widget.extend({
var refX = 0;
var resizing = false;
var minWidth = 400;
- var debounceStoreEditorWidth = _.debounce(storeEditorWidth, 500);
+ var debounceStoreEditorWidth = debounce(storeEditorWidth, 500);
this._updateViewSelectDOM();
@@ -262,8 +263,8 @@ var ViewEditor = Widget.extend({
var initType;
if (this.options.initialResID) {
initResID = this.options.initialResID;
- if (_.isString(initResID) && initResID[0] === '/') {
- if (_.str.endsWith(initResID, '.scss')) {
+ if (typeof initResID === "string" && initResID[0] === '/') {
+ if (initResID.endsWith(".scss")) {
initType = 'scss';
} else {
initType = 'js';
@@ -739,7 +740,7 @@ var ViewEditor = Widget.extend({
} else {
var onChangeSession = (function () {
this.aceEditor.off('changeSession', onChangeSession);
- _.delay(__showErrorLine.bind(this, line), 400);
+ setTimeout(__showErrorLine.bind(this, line), 400);
}).bind(this);
this.aceEditor.on('changeSession', onChangeSession);
this._displayResource(resID, this.currentType);
@@ -799,7 +800,7 @@ var ViewEditor = Widget.extend({
this.errorSession = this.aceEditor.getSession();
this.errorSessionChangeCallback = __restore.bind(this);
this.errorSession.on('change', this.errorSessionChangeCallback);
- this.errorSessionScrollCallback = _.debounce(__updateErrorLineDisplay.bind(this, line), 10);
+ this.errorSessionScrollCallback = debounce(__updateErrorLineDisplay.bind(this, line), 10);
this.errorSession.on('changeScrollTop', this.errorSessionScrollCallback);
__updateErrorLineDisplay.call(this, line);
diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js
index 677c76dce40..f0fdf84c8fa 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -11,9 +11,10 @@ import {ColorPaletteWidget} from "web_editor.ColorPalette";
import SmoothScrollOnDrag from "web.smooth_scroll_on_drag";
import {getCSSVariableValue} from "web_editor.utils";
import * as gridUtils from "@web_editor/js/common/grid_layout_utils";
-import { sprintf } from "@web/core/utils/strings";
+import { sprintf, escape } from "@web/core/utils/strings";
const QWeb = core.qweb;
import {closestElement} from "@web_editor/js/editor/odoo-editor/src/utils/utils";
+import { debounce } from "@web/core/utils/timing";
var _t = core._t;
@@ -23,7 +24,7 @@ let cacheSnippetTemplate = {};
$.extend($.expr[':'], {
o_editable: function (node, i, m) {
while (node) {
- if (node.className && _.isString(node.className)) {
+ if (node.className && typeof node.className === "string") {
if (node.className.indexOf('o_not_editable') !== -1) {
return false;
}
@@ -2011,7 +2012,7 @@ var SnippetsMenu = Widget.extend({
const range = selection.getRangeAt(0);
$(range.startContainer).closest('.o_default_snippet_text').removeClass('o_default_snippet_text');
});
- const refreshSnippetEditors = _.debounce(() => {
+ const refreshSnippetEditors = debounce(() => {
for (const snippetEditor of this.snippetEditors) {
this._mutex.exec(() => snippetEditor.destroy());
}
@@ -2042,7 +2043,7 @@ var SnippetsMenu = Widget.extend({
this.options.wysiwyg.odooEditor.addEventListener('observerApply', () => {
$(this.options.wysiwyg.odooEditor.editable).trigger('content_changed');
});
- this.options.wysiwyg.odooEditor.addEventListener('historyRevert', _.debounce(() => {
+ this.options.wysiwyg.odooEditor.addEventListener('historyRevert', debounce(() => {
this.trigger_up('widgets_start_request', {
$target: this.options.wysiwyg.$editable,
editableMode: true,
@@ -2878,8 +2879,8 @@ var SnippetsMenu = Widget.extend({
.addClass('oe_snippet')
.each((i, el) => {
const $snippet = $(el);
- const name = _.escape(el.getAttribute('name'));
- const thumbnailSrc = _.escape(el.dataset.oeThumbnail);
+ const name = escape(el.getAttribute('name'));
+ const thumbnailSrc = escape(el.dataset.oeThumbnail);
const $sbody = $snippet.children().addClass('oe_snippet_body');
const isCustomSnippet = !!el.closest('#snippet_custom');
diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js
index bb6c400c5d6..7d985db529b 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -34,6 +34,7 @@ import {
import * as OdooEditorLib from "@web_editor/js/editor/odoo-editor/src/OdooEditor";
import {SIZES, MEDIAS_BREAKPOINTS} from "@web/core/ui/ui_service";
import { sprintf } from "@web/core/utils/strings";
+import { debounce } from "@web/core/utils/timing";
var qweb = core.qweb;
var _t = core._t;
@@ -2370,7 +2371,7 @@ const RangeUserValueWidget = UnitUserValueWidget.extend({
this.containerEl.appendChild(this.outputEl);
}
- this._onInputChange = _.debounce(this._onInputChange, 100);
+ this._onInputChange = debounce(this._onInputChange, 100);
},
//--------------------------------------------------------------------------
@@ -4767,8 +4768,8 @@ registry['sizing_x'] = registry.sizing.extend({
var gridE = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
var gridW = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
this.grid = {
- e: [_.map(gridE, v => ('col-lg-' + v)), _.map(gridE, v => width / 12 * v), 'width'],
- w: [_.map(gridW, v => ('offset-lg-' + v)), _.map(gridW, v => width / 12 * v), 'margin-left'],
+ e: [gridE.map(v => ('col-lg-' + v)), gridE.map(v => width / 12 * v), 'width'],
+ w: [gridW.map(v => ('offset-lg-' + v)), gridW.map(v => width / 12 * v), 'margin-left'],
};
return this.grid;
},
@@ -4847,10 +4848,10 @@ registry['sizing_grid'] = registry.sizing.extend({
const gridE = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
this.grid = {
- n: [_.map(gridN, v => ('g-height-' + (rowEnd - v))), _.map(gridN, v => ((gridProp.rowSize + gridProp.rowGap) * (v - 1))), 'grid-row-start'],
- s: [_.map(gridS, v => ('g-height-' + (v - rowStart))), _.map(gridS, v => ((gridProp.rowSize + gridProp.rowGap) * (v - 1))), 'grid-row-end'],
- w: [_.map(gridW, v => ('g-col-lg-' + (columnEnd - v))), _.map(gridW, v => ((gridProp.columnSize + gridProp.columnGap) * (v - 1))), 'grid-column-start'],
- e: [_.map(gridE, v => ('g-col-lg-' + (v - columnStart))), _.map(gridE, v => ((gridProp.columnSize + gridProp.columnGap) * (v - 1))), 'grid-column-end'],
+ n: [gridN.map(v => ('g-height-' + (rowEnd - v))), gridN.map(v => ((gridProp.rowSize + gridProp.rowGap) * (v - 1))), 'grid-row-start'],
+ s: [gridS.map(v => ('g-height-' + (v - rowStart))), gridS.map(v => ((gridProp.rowSize + gridProp.rowGap) * (v - 1))), 'grid-row-end'],
+ w: [gridW.map(v => ('g-col-lg-' + (columnEnd - v))), gridW.map(v => ((gridProp.columnSize + gridProp.columnGap) * (v - 1))), 'grid-column-start'],
+ e: [gridE.map(v => ('g-col-lg-' + (v - columnStart))), gridE.map(v => ((gridProp.columnSize + gridProp.columnGap) * (v - 1))), 'grid-column-end'],
};
return this.grid;
diff --git a/addons/web_editor/static/src/js/wysiwyg/fonts.js b/addons/web_editor/static/src/js/wysiwyg/fonts.js
index 858f71887c9..7f8f389422d 100644
--- a/addons/web_editor/static/src/js/wysiwyg/fonts.js
+++ b/addons/web_editor/static/src/js/wysiwyg/fonts.js
@@ -90,7 +90,7 @@ export default {
var self = this;
this.fontIcons.forEach((data) => {
data.cssData = self.getCssSelectors(data.parser);
- data.alias = _.flatten(_.map(data.cssData, _.property('names')));
+ data.alias = _.map(data.cssData, _.property('names')).flat();
});
}),
};
diff --git a/addons/web_editor/static/tests/test_utils.js b/addons/web_editor/static/tests/test_utils.js
index 6d809c86d64..1d42c50103b 100644
--- a/addons/web_editor/static/tests/test_utils.js
+++ b/addons/web_editor/static/tests/test_utils.js
@@ -386,10 +386,10 @@ range(40, 127).forEach((keyCode) => {
* @param {Number} addTests
*/
var testKeyboard = function ($editable, assert, keyboardTests, addTests) {
- var tests = _.compact(_.pluck(keyboardTests, 'test'));
- var testNumber = _.compact(_.pluck(tests, 'start')).length +
- _.compact(_.pluck(tests, 'content')).length +
- _.compact(_.pluck(tests, 'check')).length +
+ var tests = _.compact(keyboardTests.map((k) => k.test));
+ var testNumber = _.compact(tests.map((test) => test.start)).length +
+ _.compact(tests.map((test) => test.content)).length +
+ _.compact(tests.map((test) => test.check)).length +
(addTests | 0);
assert.expect(testNumber);
diff --git a/addons/web_tour/static/src/tour_service/tour_utils.js b/addons/web_tour/static/src/tour_service/tour_utils.js
index 2622e36de90..d261e1a766a 100644
--- a/addons/web_tour/static/src/tour_service/tour_utils.js
+++ b/addons/web_tour/static/src/tour_service/tour_utils.js
@@ -50,7 +50,7 @@ export function getFirstVisibleElement($elements) {
}
export function getJQueryElementFromSelector(selector) {
- const iframeSplit = _.isString(selector) && selector.match(/(.*\biframe[^ ]*)(.*)/);
+ const iframeSplit = typeof selector === "string" && selector.match(/(.*\biframe[^ ]*)(.*)/);
if (iframeSplit && iframeSplit[2]) {
var $iframe = $(`${iframeSplit[1]}:not(.o_ignore_in_tour)`);
if ($iframe.is('[is-ready="false"]')) {
diff --git a/addons/web_unsplash/static/src/js/unsplash_beacon.js b/addons/web_unsplash/static/src/js/unsplash_beacon.js
index 7154b2df14a..53600aacb48 100644
--- a/addons/web_unsplash/static/src/js/unsplash_beacon.js
+++ b/addons/web_unsplash/static/src/js/unsplash_beacon.js
@@ -10,7 +10,7 @@ publicWidget.registry.UnsplashBeacon = publicWidget.Widget.extend({
* @override
*/
start: function () {
- var unsplashImages = _.map(this.$('img[src*="/unsplash/"]'), function (img) {
+ var unsplashImages = this.$('img[src*="/unsplash/"]').map((img) => {
// get image id from URL (`http://www.domain.com:1234/unsplash/xYdf5feoI/lion.jpg` -> `xYdf5feoI`)
return img.src.split('/unsplash/')[1].split('/')[0];
});
diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js
index 2a54e5f21ce..f2d51fb68ec 100644
--- a/addons/website/static/src/js/content/snippets.animation.js
+++ b/addons/website/static/src/js/content/snippets.animation.js
@@ -5,6 +5,8 @@
*/
import { loadJS } from "@web/core/assets";
+import { escape } from "@web/core/utils/strings";
+import { debounce } from "@web/core/utils/timing";
import Class from "web.Class";
import config from "web.config";
import core from "web.core";
@@ -229,7 +231,7 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
this.play(e);
clearTimeout(pauseTimer);
- pauseTimer = _.delay((function () {
+ pauseTimer = setTimeout((() => {
this.pause();
pauseTimer = null;
}).bind(this), 2000);
@@ -310,7 +312,7 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
var animationState = this._getStateCallback(elapsedTime, this._newEvent);
if (!this._newEvent
&& animationState !== undefined
- && _.isEqual(animationState, this._animationLastState)) {
+ && JSON.stringify(animationState) === JSON.stringify(this._animationLastState)) {
return;
}
this._animationLastState = animationState;
@@ -451,7 +453,7 @@ registry.slider = publicWidget.Widget.extend({
this._computeHeights();
// Initialize carousel and pause if in edit mode.
this.$el.carousel(this.editableMode ? 'pause' : undefined);
- $(window).on('resize.slider', _.debounce(() => this._computeHeights(), 250));
+ $(window).on('resize.slider', debounce(() => this._computeHeights(), 250));
return this._super.apply(this, arguments);
},
/**
@@ -524,7 +526,7 @@ registry.Parallax = Animation.extend({
*/
start: function () {
this._rebuild();
- $(window).on('resize.animation_parallax', _.debounce(this._rebuild.bind(this), 500));
+ $(window).on('resize.animation_parallax', debounce(this._rebuild.bind(this), 500));
this.modalEl = this.$target[0].closest('.modal');
if (this.modalEl) {
$(this.modalEl).on('shown.bs.modal.animation_parallax', () => {
@@ -724,7 +726,7 @@ registry.mediaVideo = publicWidget.Widget.extend(MobileYoutubeAutoplayMixin, {
// the src is saved in the 'data-src' attribute or the
// 'data-oe-expression' one (the latter is used as a workaround in 10.0
// system but should obviously be reviewed in master).
- var src = _.escape(this.$el.data('oe-expression') || this.$el.data('src'));
+ var src = escape(this.$el.data('oe-expression') || this.$el.data('src'));
// Validate the src to only accept supported domains we can trust
var m = src.match(/^(?:https?:)?\/\/([^/?#]+)/);
if (!m) {
@@ -733,7 +735,7 @@ registry.mediaVideo = publicWidget.Widget.extend(MobileYoutubeAutoplayMixin, {
}
var domain = m[1].replace(/^www\./, '');
var supportedDomains = ['youtu.be', 'youtube.com', 'youtube-nocookie.com', 'instagram.com', 'vine.co', 'player.vimeo.com', 'vimeo.com', 'dailymotion.com', 'player.youku.com', 'youku.com'];
- if (!_.contains(supportedDomains, domain)) {
+ if (!supportedDomains.includes(domain)) {
// Unsupported domain, don't inject iframe
return;
}
@@ -923,7 +925,7 @@ registry.socialShare = publicWidget.Widget.extend({
'twitter': 'https://twitter.com/intent/tweet?original_referer=' + url + '&text=' + encodeURIComponent(title + hashtags + ' - ') + url,
'linkedin': 'https://www.linkedin.com/sharing/share-offsite/?url=' + url,
};
- if (!_.contains(Object.keys(socialNetworks), social)) {
+ if (!Object.keys(socialNetworks).includes(social)) {
return;
}
var wHeight = 500;
@@ -1036,7 +1038,7 @@ registry.FullScreenHeight = publicWidget.Widget.extend({
// rules may alter the full-screen-height class behavior in some
// cases (blog...).
this._adaptSize();
- $(window).on('resize.FullScreenHeight', _.debounce(() => this._adaptSize(), 250));
+ $(window).on('resize.FullScreenHeight', debounce(() => this._adaptSize(), 250));
}
return this._super(...arguments);
},
@@ -1162,7 +1164,7 @@ registry.BottomFixedElement = publicWidget.Widget.extend({
*/
async start() {
this.$scrollingElement = $().getScrollingElement();
- this.__hideBottomFixedElements = _.debounce(() => this._hideBottomFixedElements(), 100);
+ this.__hideBottomFixedElements = debounce(() => this._hideBottomFixedElements(), 100);
this.$scrollingElement.on('scroll.bottom_fixed_element', this.__hideBottomFixedElements);
$(window).on('resize.bottom_fixed_element', this.__hideBottomFixedElements);
return this._super(...arguments);
diff --git a/addons/website/static/src/js/editor/snippets.editor.js b/addons/website/static/src/js/editor/snippets.editor.js
index efc0b7d01db..316296edfa8 100644
--- a/addons/website/static/src/js/editor/snippets.editor.js
+++ b/addons/website/static/src/js/editor/snippets.editor.js
@@ -72,11 +72,9 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
*/
_computeSnippetTemplates: function (html) {
const $html = $(html);
- const fontVariables = _.map($html.find('we-fontfamilypicker[data-variable]'), el => {
- return el.dataset.variable;
- });
+ const toFind = $html.find("we-fontfamilypicker[data-variable]").toArray();
+ const fontVariables = toFind.map((el) => el.dataset.variable);
FontFamilyPickerUserValueWidget.prototype.fontVariables = fontVariables;
-
return this._super(...arguments);
},
/**
diff --git a/addons/website/static/src/js/editor/widget_link.js b/addons/website/static/src/js/editor/widget_link.js
index 5ef1c818360..65b9ddb1d2b 100644
--- a/addons/website/static/src/js/editor/widget_link.js
+++ b/addons/website/static/src/js/editor/widget_link.js
@@ -2,6 +2,7 @@
import weWidgets from "wysiwyg.widgets";
import wUtils from "website.utils";
+import { debounce } from "@web/core/utils/timing";
weWidgets.LinkTools.include({
custom_events: Object.assign({}, weWidgets.LinkTools.prototype.custom_events || {}, {
@@ -14,7 +15,7 @@ weWidgets.LinkTools.include({
*/
init: function () {
this._super.apply(this, arguments);
- this._adaptPageAnchor = _.debounce(this._adaptPageAnchor, this.LINK_DEBOUNCE);
+ this._adaptPageAnchor = debounce(this._adaptPageAnchor, this.LINK_DEBOUNCE);
},
/**
* Allows the URL input to propose existing website pages.
diff --git a/addons/website/static/src/js/editor/wysiwyg.js b/addons/website/static/src/js/editor/wysiwyg.js
index fffbd3e8924..d85eb04993e 100644
--- a/addons/website/static/src/js/editor/wysiwyg.js
+++ b/addons/website/static/src/js/editor/wysiwyg.js
@@ -16,7 +16,7 @@ import socialMediaOptions from "@website/snippets/s_social_media/options";
* @returns {Promise}
*/
function toggleDropdown($toggles, show) {
- return Promise.all(_.map($toggles, toggle => {
+ return Promise.all($($toggles).toArray().map(toggle => {
// We must select the element via the iframe so that the event handlers
// declared on the iframe are triggered.
const $toggle = toggle.ownerDocument.defaultView.$(toggle);
diff --git a/addons/website/static/src/js/utils.js b/addons/website/static/src/js/utils.js
index 0ec09344e8f..778c38d83c3 100644
--- a/addons/website/static/src/js/utils.js
+++ b/addons/website/static/src/js/utils.js
@@ -22,7 +22,7 @@ function loadAnchors(url, body) {
resolve();
}
}).then(function (response) {
- const anchors = _.map($(response).find('[id][data-anchor=true]'), function (el) {
+ const anchors = $(response).find('[id][data-anchor=true]').toArray().map((el) => {
return '#' + el.id;
});
// Always suggest the top and the bottom of the page as internal link
@@ -123,7 +123,7 @@ function autocompleteWithPages(self, $input, options) {
* @param {jQuery} [$excluded]
*/
function onceAllImagesLoaded($element, $excluded) {
- var defs = _.map($element.find('img').addBack('img'), function (img) {
+ var defs = $element.find('img').addBack('img').map(img => {
if (img.complete || $excluded && ($excluded.is(img) || $excluded.has(img).length)) {
return; // Already loaded
}
@@ -175,7 +175,7 @@ function prompt(options, _qweb) {
text: options
};
}
- if (_.isUndefined(_qweb)) {
+ if (typeof _qweb === "undefined") {
_qweb = 'website.prompt';
}
options = Object.assign({
diff --git a/addons/website/static/src/snippets/s_chart/options.js b/addons/website/static/src/snippets/s_chart/options.js
index 72f1855e14e..955d211d4fa 100644
--- a/addons/website/static/src/snippets/s_chart/options.js
+++ b/addons/website/static/src/snippets/s_chart/options.js
@@ -170,9 +170,13 @@ options.registry.InnerChart = options.Class.extend({
} else {
// Find max value from each row/column data
const datasets = JSON.parse(dataset.data).datasets || [];
- const dataValue = _.flatten(datasets.map(el => el.data.map(data => {
- return !isNaN(parseInt(data)) ? parseInt(data) : 0;
- })));
+ const dataValue = datasets
+ .map((el) => {
+ return el.data.map((data) => {
+ return !isNaN(parseInt(data)) ? parseInt(data) : 0;
+ });
+ })
+ .flat();
// When max value is not given and min value is greater than chart
// data values
if (minValue >= Math.max(...dataValue)) {
diff --git a/addons/website/static/src/snippets/s_image_gallery/options.js b/addons/website/static/src/snippets/s_image_gallery/options.js
index ecf0fac1325..71243d3a0ea 100644
--- a/addons/website/static/src/snippets/s_image_gallery/options.js
+++ b/addons/website/static/src/snippets/s_image_gallery/options.js
@@ -278,7 +278,7 @@ options.registry.gallery = options.Class.extend({
*/
slideshow: function () {
const imageEls = this._getImages();
- const images = _.map(imageEls, img => ({
+ const images = imageEls.map((img) => ({
// Use getAttribute to get the attribute value otherwise .src
// returns the absolute url.
src: img.getAttribute('src'),
diff --git a/addons/website/static/src/snippets/s_searchbar/000.js b/addons/website/static/src/snippets/s_searchbar/000.js
index 824ec0c1ad1..cadc904d725 100644
--- a/addons/website/static/src/snippets/s_searchbar/000.js
+++ b/addons/website/static/src/snippets/s_searchbar/000.js
@@ -5,6 +5,7 @@ import publicWidget from 'web.public.widget';
import {qweb} from 'web.core';
import {Markup} from 'web.utils';
+import { debounce } from '@web/core/utils/timing';
publicWidget.registry.searchBar = publicWidget.Widget.extend({
selector: '.o_searchbar_form',
@@ -24,8 +25,8 @@ publicWidget.registry.searchBar = publicWidget.Widget.extend({
this._dp = new concurrency.DropPrevious();
- this._onInput = _.debounce(this._onInput, 400);
- this._onFocusOut = _.debounce(this._onFocusOut, 100);
+ this._onInput = debounce(this._onInput, 400);
+ this._onFocusOut = debounce(this._onFocusOut, 100);
},
/**
* @override
diff --git a/addons/website/static/src/snippets/s_website_form/000.js b/addons/website/static/src/snippets/s_website_form/000.js
index 76e30162fc5..14ab98a1528 100644
--- a/addons/website/static/src/snippets/s_website_form/000.js
+++ b/addons/website/static/src/snippets/s_website_form/000.js
@@ -8,7 +8,8 @@
import publicWidget from "web.public.widget";
import dom from "web.dom";
import concurrency from "web.concurrency";
-import { sprintf } from "@web/core/utils/strings";
+ import { sprintf } from "@web/core/utils/strings";
+ import { debounce } from "@web/core/utils/timing";
var _t = core._t;
var qweb = core.qweb;
@@ -91,7 +92,7 @@ import { sprintf } from "@web/core/utils/strings";
}
this._updateFieldsVisibility();
- this._onFieldInputDebounced = _.debounce(this._onFieldInput.bind(this), 400);
+ this._onFieldInputDebounced = debounce(this._onFieldInput.bind(this), 400);
this.$el.on('input.s_website_form', '.s_website_form_field', this._onFieldInputDebounced);
// Initialize datetimepickers
@@ -459,7 +460,7 @@ import { sprintf } from "@web/core/utils/strings";
if (invalid_inputs.length || error_fields[field_name]) {
$field.addClass('o_has_error');
$controls.addClass('is-invalid');
- if (_.isString(error_fields[field_name])) {
+ if (typeof error_fields[field_name] === "string") {
$field.popover({content: error_fields[field_name], trigger: 'hover', container: 'body', placement: 'top'});
// update error message and show it.
$field.data("bs.popover").config.content = error_fields[field_name];
diff --git a/addons/website/static/tests/tours/website_form_editor_frontend.js b/addons/website/static/tests/tours/website_form_editor_frontend.js
index e36b12062b4..bd6f62242d0 100644
--- a/addons/website/static/tests/tours/website_form_editor_frontend.js
+++ b/addons/website/static/tests/tours/website_form_editor_frontend.js
@@ -175,7 +175,7 @@ registry.category("web_tour.tours").add("website_form_editor_tour_results", {
$('body').append('');
}
};
- mailDef.then(_.bind(success, this, 'mail_mail'));
+ mailDef.then(success.bind(this, 'mail_mail'));
}
},
{
diff --git a/addons/website_event/static/src/js/website_event.js b/addons/website_event/static/src/js/website_event.js
index ce44b3e20f1..aea1cf036f8 100644
--- a/addons/website_event/static/src/js/website_event.js
+++ b/addons/website_event/static/src/js/website_event.js
@@ -44,7 +44,7 @@ var EventRegistrationForm = Widget.extend({
$('#registration_form select').each(function () {
post[$(this).attr('name')] = $(this).val();
});
- var tickets_ordered = _.some(_.map(post, function (value, key) { return parseInt(value); }));
+ var tickets_ordered = _.some(Object.values(post).map((value) => parseInt(value)));
if (!tickets_ordered) {
$('')
.text(_t('Please select at least one ticket.'))
diff --git a/addons/website_event_exhibitor/static/src/js/event_exhibitor_connect.js b/addons/website_event_exhibitor/static/src/js/event_exhibitor_connect.js
index da0a7e40d89..201cfd1bda6 100644
--- a/addons/website_event_exhibitor/static/src/js/event_exhibitor_connect.js
+++ b/addons/website_event_exhibitor/static/src/js/event_exhibitor_connect.js
@@ -1,5 +1,6 @@
/** @odoo-module alias=website_event_exhibitor.event_exhibitor_connect **/
+import { debounce } from "@web/core/utils/timing";
import Dialog from "web.Dialog";
import publicWidget from "web.public.widget";
import {Markup} from "web.utils";
@@ -63,7 +64,7 @@ publicWidget.registry.eventExhibitorConnect = publicWidget.Widget.extend({
*/
init: function () {
this._super(...arguments);
- this._onConnectClick = _.debounce(this._onConnectClick, 500, true);
+ this._onConnectClick = debounce(this._onConnectClick, 500, true);
},
/**
diff --git a/addons/website_event_track/static/src/js/event_track_reminder.js b/addons/website_event_track/static/src/js/event_track_reminder.js
index c8ebf8c832f..f095cf44985 100644
--- a/addons/website_event_track/static/src/js/event_track_reminder.js
+++ b/addons/website_event_track/static/src/js/event_track_reminder.js
@@ -1,6 +1,7 @@
/** @odoo-module alias=website_event_track.website_event_track_reminder **/
import { sprintf } from "@web/core/utils/strings";
+import { debounce } from "@web/core/utils/timing";
import core from "web.core";
var _t = core._t;
import publicWidget from "web.public.widget";
@@ -16,7 +17,7 @@ publicWidget.registry.websiteEventTrackReminder = publicWidget.Widget.extend({
*/
init: function () {
this._super.apply(this, arguments);
- this._onReminderToggleClick = _.debounce(this._onReminderToggleClick, 500, true);
+ this._onReminderToggleClick = debounce(this._onReminderToggleClick, 500, true);
},
//--------------------------------------------------------------------------
diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js
index e2447d4a416..023b81e9acb 100644
--- a/addons/website_forum/static/src/js/website_forum.js
+++ b/addons/website_forum/static/src/js/website_forum.js
@@ -8,6 +8,7 @@ import wysiwygLoader from "web_editor.loader";
import publicWidget from "web.public.widget";
import { Markup } from "web.utils";
import session from "web.session";
+import { escape } from "@web/core/utils/strings";
var qweb = core.qweb;
var _t = core._t;
@@ -76,9 +77,9 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
},
formatResult: function (term) {
if (term.isNew) {
- return 'New ' + _.escape(term.text);
+ return 'New ' + escape(term.text);
} else {
- return _.escape(term.text);
+ return escape(term.text);
}
},
ajax: {
@@ -616,9 +617,7 @@ publicWidget.registry.websiteForumSpam = publicWidget.Widget.extend({
_onMarkSpamClick: function (ev) {
var key = this.$('.modal .tab-pane.active').data('key');
var $inputs = this.$('.modal .tab-pane.active input.form-check-input:checked');
- var values = _.map($inputs, function (o) {
- return parseInt(o.value);
- });
+ var values = $inputs.map((o) => parseInt(o.value));
return this._rpc({model: 'forum.post',
method: 'mark_as_offensive_batch',
args: [this.spamIDs, key, values],
diff --git a/addons/website_links/static/src/js/website_links.js b/addons/website_links/static/src/js/website_links.js
index e349a5475ee..3d0c0b8d922 100644
--- a/addons/website_links/static/src/js/website_links.js
+++ b/addons/website_links/static/src/js/website_links.js
@@ -34,7 +34,7 @@ var SelectBox = publicWidget.Widget.extend({
fields: ['id', 'name'],
},
}).then(function (result) {
- self.objects = _.map(result, function (val) {
+ self.objects = result.map((val) => {
return {id: val.id, text: val.name};
});
}));
@@ -103,7 +103,7 @@ var SelectBox = publicWidget.Widget.extend({
* @param {Object} ev
*/
_onChange: function (ev) {
- if (!ev.added || !_.isString(ev.added.id)) {
+ if (!ev.added || typeof ev.added.id !== "string") {
return;
}
this._createObject(ev.added.id);
@@ -327,7 +327,9 @@ var RecentLinks = publicWidget.Widget.extend({
* @private
*/
removeLinks: function () {
- _.invoke(this.getChildren(), 'destroy');
+ this.getChildren().forEach((child) => {
+ child.destroy();
+ });
},
/**
* @private
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 6659c0dae0b..643edb77c30 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -12,6 +12,7 @@ import dom from "web.dom";
import { cartesian } from "@web/core/utils/arrays";
import { ComponentWrapper } from "web.OwlCompatibility";
import { ProductImageViewerWrapper } from "@website_sale/js/components/website_sale_image_viewer";
+import { debounce } from "@web/core/utils/timing";
publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, cartHandlerMixin, {
selector: '.oe_website_sale',
@@ -49,8 +50,8 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
init: function () {
this._super.apply(this, arguments);
- this._changeCartQuantity = _.debounce(this._changeCartQuantity.bind(this), 500);
- this._changeCountry = _.debounce(this._changeCountry.bind(this), 500);
+ this._changeCartQuantity = debounce(this._changeCartQuantity.bind(this), 500);
+ this._changeCountry = debounce(this._changeCountry.bind(this), 500);
this.isWebsite = true;
this.filmStripStartX = 0;
@@ -181,9 +182,7 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
*/
_setUrlHash: function ($parent) {
var $attributes = $parent.find('input.js_variant_change:checked, select.js_variant_change option:selected');
- var attributeIds = _.map($attributes, function (elem) {
- return $(elem).data('value_id');
- });
+ var attributeIds = $attributes.toArray().map((elem) => $(elem).data("value_id"));
window.location.replace('#attr=' + attributeIds.join(','));
},
/**
diff --git a/addons/website_sale/static/src/js/website_sale_payment.js b/addons/website_sale/static/src/js/website_sale_payment.js
index 74b52ef9bd4..af1e7522016 100644
--- a/addons/website_sale/static/src/js/website_sale_payment.js
+++ b/addons/website_sale/static/src/js/website_sale_payment.js
@@ -1,6 +1,7 @@
/** @odoo-module **/
- import checkoutForm from "payment.checkout_form";
+ import { debounce } from "@web/core/utils/timing";
+import checkoutForm from "payment.checkout_form";
import publicWidget from "web.public.widget";
const websiteSalePaymentMixin = {
@@ -9,7 +10,7 @@
* @override
*/
init: function () {
- this._onClickTCCheckbox = _.debounce(this._onClickTCCheckbox, 100, true);
+ this._onClickTCCheckbox = debounce(this._onClickTCCheckbox, 100, true);
this._super(...arguments);
},
@@ -60,9 +61,7 @@
* @return {boolean} Whether the submit button can be enabled
*/
_isButtonReady: function () {
- const disabledReasonFound = _.contains(
- this.$submitButton.data('disabled_reasons'), true
- );
+ const disabledReasonFound = this.$submitButton.data("disabled_reasons");
return !disabledReasonFound && this._super();
},
@@ -115,10 +114,7 @@
*/
_onClickTCCheckbox: function () {
this._adaptConfirmButton();
-
- const disabledReasonFound = _.contains(
- this.$submitButton.data('disabled_reasons'), true
- );
+ const disabledReasonFound = this.$submitButton.data("disabled_reasons");
this.$submitButton.prop('disabled', disabledReasonFound);
},
}));
diff --git a/addons/website_sale/static/src/js/website_sale_recently_viewed.js b/addons/website_sale/static/src/js/website_sale_recently_viewed.js
index e68507e0846..37cddd3376a 100644
--- a/addons/website_sale/static/src/js/website_sale_recently_viewed.js
+++ b/addons/website_sale/static/src/js/website_sale_recently_viewed.js
@@ -1,5 +1,6 @@
/** @odoo-module **/
+import { debounce } from "@web/core/utils/timing";
import publicWidget from "web.public.widget";
import {getCookie, setCookie} from "web.utils.cookies";
@@ -15,7 +16,7 @@ publicWidget.registry.productsRecentlyViewedUpdate = publicWidget.Widget.extend(
*/
init: function () {
this._super.apply(this, arguments);
- this._onProductChange = _.debounce(this._onProductChange, this.debounceValue);
+ this._onProductChange = debounce(this._onProductChange, this.debounceValue);
},
//--------------------------------------------------------------------------
diff --git a/addons/website_sale_comparison/static/src/js/website_sale_comparison.js b/addons/website_sale_comparison/static/src/js/website_sale_comparison.js
index 12443567995..0052371181a 100644
--- a/addons/website_sale_comparison/static/src/js/website_sale_comparison.js
+++ b/addons/website_sale_comparison/static/src/js/website_sale_comparison.js
@@ -68,7 +68,7 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, {
self._removeFromComparelist(ev);
self.guard.exec(function() {
const newLink = '/shop/compare?products=' + encodeURIComponent(self.comparelist_product_ids);
- window.location.href = _.isEmpty(self.comparelist_product_ids) ? '/shop' : newLink;
+ window.location.href = Object.keys(self.comparelist_product_ids || {}).length === 0 ? '/shop' : newLink;
});
});
@@ -172,7 +172,7 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, {
_addNewProductsImpl: function (product_id) {
var self = this;
$('.o_product_feature_panel').addClass('d-md-block');
- if (!_.contains(self.comparelist_product_ids, product_id)) {
+ if (!self.comparelist_product_ids.includes(product_id)) {
self.comparelist_product_ids.push(product_id);
if (_.has(self.product_data, product_id)){
self._updateContent();
@@ -233,7 +233,7 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, {
_updateComparelistView: function () {
this.$('.o_product_circle').text(this.comparelist_product_ids.length);
this.$('.o_comparelist_button').removeClass('d-md-block');
- if (_.isEmpty(this.comparelist_product_ids)) {
+ if (Object.keys(this.comparelist_product_ids || {}).length === 0) {
$('.o_product_feature_panel').removeClass('d-md-block');
} else {
$('.o_product_feature_panel').addClass('d-md-block');
diff --git a/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js b/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js
index 51038e9b7d7..c0099db6361 100644
--- a/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js
+++ b/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js
@@ -96,7 +96,7 @@ publicWidget.registry.ProductWishlist = publicWidget.Widget.extend(VariantMixin,
productReady.then(function (productId) {
productId = parseInt(productId, 10);
- if (productId && !_.contains(self.wishlistProductIDs, productId)) {
+ if (productId && !self.wishlistProductIDs.includes(productId)) {
return self._rpc({
route: '/shop/wishlist/add',
params: {
@@ -250,7 +250,7 @@ publicWidget.registry.ProductWishlist = publicWidget.Widget.extend(VariantMixin,
var $input = $(ev.target);
var $parent = $input.closest('.js_product');
var $el = $parent.find("[data-action='o_wishlist']");
- if (!_.contains(this.wishlistProductIDs, parseInt($input.val(), 10))) {
+ if (!this.wishlistProductIDs.includes(parseInt($input.val(), 10))) {
$el.prop("disabled", false).removeClass('disabled').removeAttr('disabled');
} else {
$el.prop("disabled", true).addClass('disabled').attr('disabled', 'disabled');
@@ -265,7 +265,7 @@ publicWidget.registry.ProductWishlist = publicWidget.Widget.extend(VariantMixin,
var productID = ev.currentTarget.value;
var $el = $(ev.target).closest('.js_add_cart_variants').find("[data-action='o_wishlist']");
- if (!_.contains(this.wishlistProductIDs, parseInt(productID, 10))) {
+ if (!this.wishlistProductIDs.includes(parseInt(productID, 10))) {
$el.prop("disabled", false).removeClass('disabled').removeAttr('disabled');
} else {
$el.prop("disabled", true).addClass('disabled').attr('disabled', 'disabled');
diff --git a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
index 50ca540a39e..7ceb780e685 100644
--- a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
+++ b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
@@ -450,9 +450,7 @@
clipboard.on('success', function () {
clipboard.destroy();
$clipboardBtn.tooltip('show');
- _.delay(function () {
- $clipboardBtn.tooltip("hide");
- }, 800);
+ setTimeout(() => $clipboardBtn.tooltip("hide"), 800);
});
clipboard.on('error', function (e) {
clipboard.destroy();
@@ -619,7 +617,7 @@
// technical settings for the Fullscreen to work
var autoSetDone = false;
if (!slideData.hasQuestion) {
- if (_.contains(['infographic', 'document', 'article'], slideData.category)) {
+ if (['infographic', 'document', 'article'].includes(slideData.category)) {
autoSetDone = true; // images, documents (local + external) and articles are marked as completed when opened
} else if (slideData.category === 'video' && slideData.videoSourceType === 'google_drive') {
autoSetDone = true; // google drive videos do not benefit from the YouTube integration and are marked as completed when opened
@@ -669,7 +667,7 @@
}
// render slide content
- if (_.contains(['document', 'infographic'], slide.category)) {
+ if (['document', 'infographic'].includes(slide.category)) {
$content.html(QWeb.render('website.slides.fullscreen.content', {widget: this}));
} else if (slide.category === 'video' && slide.videoSourceType === 'youtube') {
this.videoPlayer = new VideoPlayerYouTube(this, slide);
diff --git a/addons/website_slides/static/src/js/slides_course_quiz.js b/addons/website_slides/static/src/js/slides_course_quiz.js
index 6d503a16e6c..538df35bf3a 100644
--- a/addons/website_slides/static/src/js/slides_course_quiz.js
+++ b/addons/website_slides/static/src/js/slides_course_quiz.js
@@ -280,7 +280,7 @@
$question.find('a.o_wslides_quiz_answer').each(function () {
var $answer = $(this);
if (!$answer.find('input[type=radio]')[0].checked &&
- _.contains(self.slide.sessionAnswers, $answer.data('answerId'))) {
+ self.slide.sessionAnswers.includes($answer.data('answerId'))) {
$answer.find('input[type=radio]').prop('checked', true);
}
});
diff --git a/addons/website_slides/static/src/js/slides_course_quiz_finish.js b/addons/website_slides/static/src/js/slides_course_quiz_finish.js
index a6384bb50bb..0c048074aeb 100644
--- a/addons/website_slides/static/src/js/slides_course_quiz_finish.js
+++ b/addons/website_slides/static/src/js/slides_course_quiz_finish.js
@@ -100,17 +100,17 @@ var SlideQuizFinishModal = Dialog.extend({
if (this.quiz.rankProgress.level_up) {
this.$('.o_wslides_quiz_modal_title').text(_t('Level up!'));
$progressBar.css('width', '100%');
- _.delay(function () {
+ setTimeout(() => {
self.$('.o_wslides_quiz_modal_rank_lower_bound')
.text(self.quiz.rankProgress.new_rank.lower_bound);
self.$('.o_wslides_quiz_modal_rank_upper_bound')
.text(self.quiz.rankProgress.new_rank.upper_bound || "");
// we need to use _.delay to force DOM re-rendering between 0 and new percentage
- _.delay(function () {
+ setTimeout(() => {
$progressBar.addClass('no-transition').width('0%');
}, 1);
- _.delay(function () {
+ setTimeout(() => {
$progressBar
.removeClass('no-transition')
.width(self.quiz.rankProgress.new_rank.progress + '%');
@@ -128,15 +128,15 @@ var SlideQuizFinishModal = Dialog.extend({
*/
_animateText: function () {
var self = this;
- _.delay(function () {
+ setTimeout(function () {
self.$('h4.o_wslides_quiz_modal_xp_gained').addClass('show in');
self.$('.o_wslides_quiz_modal_dismiss').removeClass('d-none');
}, 800);
if (this.quiz.rankProgress.level_up) {
- _.delay(function () {
+ setTimeout(function () {
self.$('.o_wslides_quiz_modal_rank_motivational').addClass('fade');
- _.delay(function () {
+ setTimeout(function () {
self.$('.o_wslides_quiz_modal_rank_motivational').html(
self.quiz.rankProgress.last_rank ?
self.quiz.rankProgress.description :
diff --git a/addons/website_slides/static/src/js/slides_embed.js b/addons/website_slides/static/src/js/slides_embed.js
index 0e83b41feb2..5a6e5a4fc52 100644
--- a/addons/website_slides/static/src/js/slides_embed.js
+++ b/addons/website_slides/static/src/js/slides_embed.js
@@ -1,4 +1,5 @@
/* global PDFSlidesViewer */
+
/**
* This is a minimal version of the PDFViewer widget.
* It is NOT use in the website_slides module, but it is called when embedding
@@ -7,6 +8,14 @@
*/
$(function () {
+ function debounce(func, timeout = 300){
+ let timer;
+ return (...args) => {
+ clearTimeout(timer);
+ timer = setTimeout(() => { func.apply(this, args); }, timeout);
+ };
+ }
+
if ($('#PDFViewer') && $('#PDFViewerCanvas')) { // check if presentation only
var MIN_ZOOM=1, MAX_ZOOM=10, ZOOM_INCREMENT=.5;
@@ -177,7 +186,7 @@ $(function () {
return false;
}
});
- $(window).on('resize', _.debounce(function() {
+ $(window).on("resize", debounce(() => {
embeddedViewer.on_resize();
}, 500));
diff --git a/addons/website_slides/static/src/js/slides_share.js b/addons/website_slides/static/src/js/slides_share.js
index d961accd446..cdb64ba6ca6 100644
--- a/addons/website_slides/static/src/js/slides_share.js
+++ b/addons/website_slides/static/src/js/slides_share.js
@@ -121,9 +121,7 @@ publicWidget.registry.websiteSlidesShare = publicWidget.Widget.extend({
clipboard.on('success', function () {
clipboard.destroy();
$clipboardBtn.tooltip('show');
- _.delay(function () {
- $clipboardBtn.tooltip("hide");
- }, 800);
+ setTimeout(() => $clipboardBtn.tooltip("hide"), 800);
});
clipboard.on('error', function (e) {
console.log(e);
@@ -152,7 +150,7 @@ publicWidget.registry.websiteSlidesEmbedShare = publicWidget.Widget.extend({
clipboard.on('success', function () {
clipboard.destroy();
$clipboardBtn.tooltip('show');
- _.delay(function () {
+ setTimeout(function () {
$clipboardBtn.tooltip("hide");
}, 800);
});
diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js
index d1df29820b6..247d7b5f5a2 100644
--- a/addons/website_slides/static/src/js/slides_upload.js
+++ b/addons/website_slides/static/src/js/slides_upload.js
@@ -147,7 +147,7 @@ var SlideUploadDialog = Dialog.extend({
_formValidateGetValues: async function (forcePublished) {
var slideCategory = 'document';
// default slide_category (for article for instance)
- if (_.contains(this.slide_category_data), this.get('state')) {
+ if (Object.keys(this.slide_category_data).includes(this.get("state"))) {
slideCategory = this.get('state');
}
diff --git a/addons/website_twitter/static/src/js/website.twitter.animation.js b/addons/website_twitter/static/src/js/website.twitter.animation.js
index 6cc6b637502..c0d99e3a8e1 100644
--- a/addons/website_twitter/static/src/js/website.twitter.animation.js
+++ b/addons/website_twitter/static/src/js/website.twitter.animation.js
@@ -3,6 +3,7 @@
import core from "web.core";
import {Markup} from "web.utils";
import publicWidget from "web.public.widget";
+import { escape } from "@web/core/utils/strings";
var qweb = core.qweb;
@@ -31,13 +32,13 @@ publicWidget.registry.twitter = publicWidget.Widget.extend({
return;
}
- if (_.isEmpty(data)) {
+ if (Object.keys(data || {}).length === 0) {
return;
}
- var tweets = _.map(data, function (tweet) {
+ var tweets = data.map((tweet) => {
// Parse tweet date
- if (_.isEmpty(tweet.created_at)) {
+ if (Object.keys(tweet.created_at || {}).length === 0) {
tweet.created_at = '';
} else {
var v = tweet.created_at.split(' ');
@@ -46,7 +47,7 @@ publicWidget.registry.twitter = publicWidget.Widget.extend({
}
// Parse tweet text
- tweet.text = Markup(_.escape(tweet.text)
+ tweet.text = Markup(escape(tweet.text)
.replace(
/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&~\?\/.=]+/g,
function (url) {