summaryrefslogtreecommitdiff
path: root/addons/website/static/src/js/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/website/static/src/js/utils.js')
-rw-r--r--addons/website/static/src/js/utils.js295
1 files changed, 295 insertions, 0 deletions
diff --git a/addons/website/static/src/js/utils.js b/addons/website/static/src/js/utils.js
new file mode 100644
index 00000000..1c0edf43
--- /dev/null
+++ b/addons/website/static/src/js/utils.js
@@ -0,0 +1,295 @@
+odoo.define('website.utils', function (require) {
+'use strict';
+
+var ajax = require('web.ajax');
+var core = require('web.core');
+
+var qweb = core.qweb;
+
+/**
+ * Allows to load anchors from a page.
+ *
+ * @param {string} url
+ * @returns {Deferred<string[]>}
+ */
+function loadAnchors(url) {
+ return new Promise(function (resolve, reject) {
+ if (url === window.location.pathname || url[0] === '#') {
+ resolve(document.body.outerHTML);
+ } else if (url.length && !url.startsWith("http")) {
+ $.get(window.location.origin + url).then(resolve, reject);
+ } else { // avoid useless query
+ resolve();
+ }
+ }).then(function (response) {
+ return _.map($(response).find('[id][data-anchor=true]'), function (el) {
+ return '#' + el.id;
+ });
+ }).catch(error => {
+ console.debug(error);
+ return [];
+ });
+}
+
+/**
+ * Allows the given input to propose existing website URLs.
+ *
+ * @param {ServicesMixin|Widget} self - an element capable to trigger an RPC
+ * @param {jQuery} $input
+ */
+function autocompleteWithPages(self, $input, options) {
+ $.widget("website.urlcomplete", $.ui.autocomplete, {
+ options: options || {},
+ _create: function () {
+ this._super();
+ this.widget().menu("option", "items", "> :not(.ui-autocomplete-category)");
+ },
+ _renderMenu: function (ul, items) {
+ const self = this;
+ items.forEach(item => {
+ if (item.separator) {
+ self._renderSeparator(ul, item);
+ }
+ else {
+ self._renderItem(ul, item);
+ }
+ });
+ },
+ _renderSeparator: function (ul, item) {
+ return $("<li class='ui-autocomplete-category font-weight-bold text-capitalize p-2'>")
+ .append(`<div>${item.separator}</div>`)
+ .appendTo(ul);
+ },
+ _renderItem: function (ul, item) {
+ return $("<li>")
+ .data('ui-autocomplete-item', item)
+ .append(`<div>${item.label}</div>`)
+ .appendTo(ul);
+ },
+ });
+ $input.urlcomplete({
+ source: function (request, response) {
+ if (request.term[0] === '#') {
+ loadAnchors(request.term).then(function (anchors) {
+ response(anchors);
+ });
+ } else if (request.term.startsWith('http') || request.term.length === 0) {
+ // avoid useless call to /website/get_suggested_links
+ response();
+ } else {
+ return self._rpc({
+ route: '/website/get_suggested_links',
+ params: {
+ needle: request.term,
+ limit: 15,
+ }
+ }).then(function (res) {
+ let choices = res.matching_pages;
+ res.others.forEach(other => {
+ if (other.values.length) {
+ choices = choices.concat(
+ [{separator: other.title}],
+ other.values,
+ );
+ }
+ });
+ response(choices);
+ });
+ }
+ },
+ select: function (ev, ui) {
+ // choose url in dropdown with arrow change ev.target.value without trigger_up
+ // so cannot check here if value has been updated
+ ev.target.value = ui.item.value;
+ self.trigger_up('website_url_chosen');
+ ev.preventDefault();
+ },
+ });
+}
+
+/**
+ * @param {jQuery} $element
+ * @param {jQuery} [$excluded]
+ */
+function onceAllImagesLoaded($element, $excluded) {
+ var defs = _.map($element.find('img').addBack('img'), function (img) {
+ if (img.complete || $excluded && ($excluded.is(img) || $excluded.has(img).length)) {
+ return; // Already loaded
+ }
+ var def = new Promise(function (resolve, reject) {
+ $(img).one('load', function () {
+ resolve();
+ });
+ });
+ return def;
+ });
+ return Promise.all(defs);
+}
+
+/**
+ * @deprecated
+ * @todo create Dialog.prompt instead of this
+ */
+function prompt(options, _qweb) {
+ /**
+ * A bootstrapped version of prompt() albeit asynchronous
+ * This was built to quickly prompt the user with a single field.
+ * For anything more complex, please use editor.Dialog class
+ *
+ * Usage Ex:
+ *
+ * website.prompt("What... is your quest ?").then(function (answer) {
+ * arthur.reply(answer || "To seek the Holy Grail.");
+ * });
+ *
+ * website.prompt({
+ * select: "Please choose your destiny",
+ * init: function () {
+ * return [ [0, "Sub-Zero"], [1, "Robo-Ky"] ];
+ * }
+ * }).then(function (answer) {
+ * mame_station.loadCharacter(answer);
+ * });
+ *
+ * @param {Object|String} options A set of options used to configure the prompt or the text field name if string
+ * @param {String} [options.window_title=''] title of the prompt modal
+ * @param {String} [options.input] tell the modal to use an input text field, the given value will be the field title
+ * @param {String} [options.textarea] tell the modal to use a textarea field, the given value will be the field title
+ * @param {String} [options.select] tell the modal to use a select box, the given value will be the field title
+ * @param {Object} [options.default=''] default value of the field
+ * @param {Function} [options.init] optional function that takes the `field` (enhanced with a fillWith() method) and the `dialog` as parameters [can return a promise]
+ */
+ if (typeof options === 'string') {
+ options = {
+ text: options
+ };
+ }
+ var xmlDef;
+ if (_.isUndefined(_qweb)) {
+ _qweb = 'website.prompt';
+ xmlDef = ajax.loadXML('/website/static/src/xml/website.xml', core.qweb);
+ }
+ options = _.extend({
+ window_title: '',
+ field_name: '',
+ 'default': '', // dict notation for IE<9
+ init: function () {},
+ }, options || {});
+
+ var type = _.intersection(Object.keys(options), ['input', 'textarea', 'select']);
+ type = type.length ? type[0] : 'input';
+ options.field_type = type;
+ options.field_name = options.field_name || options[type];
+
+ var def = new Promise(function (resolve, reject) {
+ Promise.resolve(xmlDef).then(function () {
+ var dialog = $(qweb.render(_qweb, options)).appendTo('body');
+ options.$dialog = dialog;
+ var field = dialog.find(options.field_type).first();
+ field.val(options['default']); // dict notation for IE<9
+ field.fillWith = function (data) {
+ if (field.is('select')) {
+ var select = field[0];
+ data.forEach(function (item) {
+ select.options[select.options.length] = new window.Option(item[1], item[0]);
+ });
+ } else {
+ field.val(data);
+ }
+ };
+ var init = options.init(field, dialog);
+ Promise.resolve(init).then(function (fill) {
+ if (fill) {
+ field.fillWith(fill);
+ }
+ dialog.modal('show');
+ field.focus();
+ dialog.on('click', '.btn-primary', function () {
+ var backdrop = $('.modal-backdrop');
+ resolve({ val: field.val(), field: field, dialog: dialog });
+ dialog.modal('hide').remove();
+ backdrop.remove();
+ });
+ });
+ dialog.on('hidden.bs.modal', function () {
+ var backdrop = $('.modal-backdrop');
+ reject();
+ dialog.remove();
+ backdrop.remove();
+ });
+ if (field.is('input[type="text"], select')) {
+ field.keypress(function (e) {
+ if (e.which === 13) {
+ e.preventDefault();
+ dialog.find('.btn-primary').trigger('click');
+ }
+ });
+ }
+ });
+ });
+
+ return def;
+}
+
+function websiteDomain(self) {
+ var websiteID;
+ self.trigger_up('context_get', {
+ callback: function (ctx) {
+ websiteID = ctx['website_id'];
+ },
+ });
+ return ['|', ['website_id', '=', false], ['website_id', '=', websiteID]];
+}
+
+function sendRequest(route, params) {
+ function _addInput(form, name, value) {
+ let param = document.createElement('input');
+ param.setAttribute('type', 'hidden');
+ param.setAttribute('name', name);
+ param.setAttribute('value', value);
+ form.appendChild(param);
+ }
+
+ let form = document.createElement('form');
+ form.setAttribute('action', route);
+ form.setAttribute('method', params.method || 'POST');
+
+ if (core.csrf_token) {
+ _addInput(form, 'csrf_token', core.csrf_token);
+ }
+
+ for (const key in params) {
+ const value = params[key];
+ if (Array.isArray(value) && value.length) {
+ for (const val of value) {
+ _addInput(form, key, val);
+ }
+ } else {
+ _addInput(form, key, value);
+ }
+ }
+
+ document.body.appendChild(form);
+ form.submit();
+}
+
+/**
+ * Removes the navigation-blocking fullscreen loader from the DOM
+ */
+function removeLoader() {
+ const $loader = $('#o_website_page_loader');
+ if ($loader) {
+ $loader.remove();
+ }
+}
+
+return {
+ loadAnchors: loadAnchors,
+ autocompleteWithPages: autocompleteWithPages,
+ onceAllImagesLoaded: onceAllImagesLoaded,
+ prompt: prompt,
+ sendRequest: sendRequest,
+ websiteDomain: websiteDomain,
+ removeLoader: removeLoader,
+};
+});