summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/fields/special_fields.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/fields/special_fields.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/fields/special_fields.js')
-rw-r--r--addons/web/static/src/js/fields/special_fields.js262
1 files changed, 262 insertions, 0 deletions
diff --git a/addons/web/static/src/js/fields/special_fields.js b/addons/web/static/src/js/fields/special_fields.js
new file mode 100644
index 00000000..52ef6d51
--- /dev/null
+++ b/addons/web/static/src/js/fields/special_fields.js
@@ -0,0 +1,262 @@
+odoo.define('web.special_fields', function (require) {
+"use strict";
+
+var core = require('web.core');
+var field_utils = require('web.field_utils');
+var relational_fields = require('web.relational_fields');
+var AbstractField = require('web.AbstractField');
+
+var FieldSelection = relational_fields.FieldSelection;
+var _t = core._t;
+var _lt = core._lt;
+
+
+/**
+ * This widget is intended to display a warning near a label of a 'timezone' field
+ * indicating if the browser timezone is identical (or not) to the selected timezone.
+ * This widget depends on a field given with the param 'tz_offset_field', which contains
+ * the time difference between UTC time and local time, in minutes.
+ */
+var FieldTimezoneMismatch = FieldSelection.extend({
+ /**
+ * @override
+ */
+ start: function () {
+ var interval = navigator.platform.toUpperCase().indexOf('MAC') >= 0 ? 60000 : 1000;
+ this._datetime = setInterval(this._renderDateTimeTimezone.bind(this), interval);
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * @override
+ */
+ destroy: function () {
+ clearInterval(this._datetime);
+ return this._super();
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @private
+ */
+ _render: function () {
+ this._super.apply(this, arguments);
+ this._renderTimezoneMismatch();
+ },
+ /**
+ * Display the time in the user timezone (reload each second)
+ *
+ * @private
+ */
+ _renderDateTimeTimezone: function () {
+ if (!this.mismatch || !this.$option.html()) {
+ return;
+ }
+ var offset = this.recordData.tz_offset.match(/([+-])([0-9]{2})([0-9]{2})/);
+ offset = (offset[1] === '-' ? -1 : 1) * (parseInt(offset[2])*60 + parseInt(offset[3]));
+ var datetime = field_utils.format.datetime(moment.utc().add(offset, 'minutes'), this.field, {timezone: false});
+ var content = this.$option.html().split(' ')[0];
+ content += ' ('+ datetime + ')';
+ this.$option.html(content);
+ },
+ /**
+ * Display the timezone alert
+ *
+ * Note: timezone alert is a span that is added after $el, and $el is now a
+ * set of two elements
+ *
+ * @private
+ */
+ _renderTimezoneMismatch: function () {
+ // we need to clean the warning to have maximum one alert
+ this.$el.last().filter('.o_tz_warning').remove();
+ this.$el = this.$el.first();
+ var value = this.$el.val();
+ var $span = $('<span class="fa fa-exclamation-triangle o_tz_warning"/>');
+
+ if (this.$option && this.$option.html()) {
+ this.$option.html(this.$option.html().split(' ')[0]);
+ }
+
+ var userOffset = this.recordData.tz_offset;
+ this.mismatch = false;
+ if (userOffset && value !== "" && value !== "false") {
+ var offset = -(new Date().getTimezoneOffset());
+ var browserOffset = (offset < 0) ? "-" : "+";
+ browserOffset += _.str.sprintf("%02d", Math.abs(offset / 60));
+ browserOffset += _.str.sprintf("%02d", Math.abs(offset % 60));
+ this.mismatch = (browserOffset !== userOffset);
+ }
+
+ if (this.mismatch){
+ $span.insertAfter(this.$el);
+ $span.attr('title', _t("Timezone Mismatch : This timezone is different from that of your browser.\nPlease, set the same timezone as your browser's to avoid time discrepancies in your system."));
+ this.$el = this.$el.add($span);
+
+ this.$option = this.$('option').filter(function () {
+ return $(this).attr('value') === value;
+ });
+ this._renderDateTimeTimezone();
+ } else if (value == "false") {
+ $span.insertAfter(this.$el);
+ $span.attr('title', _t("Set a timezone on your user"));
+ this.$el = this.$el.add($span);
+ }
+ },
+ /**
+ * @override
+ * @private
+ * this.$el can have other elements than select
+ * that should not be touched
+ */
+ _renderEdit: function () {
+ // FIXME: hack to handle multiple root elements
+ // in this.$el , which is a bad idea
+ // In master we should make this.$el a wrapper
+ // around multiple subelements
+ var $otherEl = this.$el.not('select');
+ this.$el = this.$el.first();
+
+ this._super.apply(this, arguments);
+
+ $otherEl.insertAfter(this.$el);
+ this.$el = this.$el.add($otherEl);
+ },
+});
+
+var FieldReportLayout = relational_fields.FieldMany2One.extend({
+ // this widget is not generic, so we disable its studio use
+ // supportedFieldTypes: ['many2one', 'selection'],
+ events: _.extend({}, relational_fields.FieldMany2One.prototype.events, {
+ 'click img': '_onImgClicked',
+ }),
+
+ willStart: function () {
+ var self = this;
+ this.previews = {};
+ return this._super()
+ .then(function () {
+ return self._rpc({
+ model: 'report.layout',
+ method: "search_read"
+ }).then(function (values) {
+ self.previews = values;
+ });
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @private
+ */
+ _render: function () {
+ var self = this;
+ this.$el.empty();
+ var value = _.isObject(this.value) ? this.value.data.id : this.value;
+ _.each(this.previews, function (val) {
+ var $container = $('<div>').addClass('col-3 text-center');
+ var $img = $('<img>')
+ .addClass('img img-fluid img-thumbnail ml16')
+ .toggleClass('btn-info', val.view_id[0] === value)
+ .attr('src', val.image)
+ .data('key', val.view_id[0]);
+ $container.append($img);
+ if (val.pdf) {
+ var $previewLink = $('<a>')
+ .text('Example')
+ .attr('href', val.pdf)
+ .attr('target', '_blank');
+ $container.append($previewLink);
+ }
+ self.$el.append($container);
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @private
+ * @param {MouseEvent} event
+ */
+ _onImgClicked: function (event) {
+ this._setValue($(event.currentTarget).data('key'));
+ },
+});
+
+
+const IframeWrapper = AbstractField.extend({
+ description: _lt("Wrap raw html within an iframe"),
+
+ // If HTML, don't forget to adjust the sanitize options to avoid stripping most of the metadata
+ supportedFieldTypes: ['text', 'html'],
+
+ template: "web.IframeWrapper",
+
+ _render() {
+
+ const spinner = this.el.querySelector('.o_iframe_wrapper_spinner');
+ const iframe = this.el.querySelector('.o_preview_iframe');
+
+ iframe.style.display = 'none';
+ spinner.style.display = 'block';
+
+ // Promise for tests
+ let resolver;
+ $(iframe).data('ready', new Promise((resolve) => {
+ resolver = resolve;
+ }));
+
+ /**
+ * Certain browser don't trigger onload events of iframe for particular cases.
+ * In our case, chrome and safari could be problematic depending on version and environment.
+ * This rather unorthodox solution replace the onload event handler. (jquery on('load') doesn't fix it)
+ */
+ const onloadReplacement = setInterval(() => {
+ const iframeDoc = iframe.contentDocument;
+ if (iframeDoc && (iframeDoc.readyState === 'complete' || iframeDoc.readyState === 'interactive')) {
+
+ /**
+ * The document.write is not recommended. It is better to manipulate the DOM through $.appendChild and
+ * others. In our case though, we deal with an iframe without src attribute and with metadata to put in
+ * head tag. If we use the usual dom methods, the iframe is automatically created with its document
+ * component containing html > head & body. Therefore, if we want to make it work that way, we would
+ * need to receive each piece at a time to append it to this document (with this.record.data and extra
+ * model fields or with an rpc). It also cause other difficulties getting attribute on the most parent
+ * nodes, parsing to HTML complex elements, etc.
+ * Therefore, document.write makes it much more trivial in our situation.
+ */
+ iframeDoc.open();
+ iframeDoc.write(this.value);
+ iframeDoc.close();
+
+ iframe.style.display = 'block';
+ spinner.style.display = 'none';
+
+ resolver();
+
+ clearInterval(onloadReplacement);
+ }
+ }, 100);
+
+ }
+
+});
+
+
+return {
+ FieldTimezoneMismatch: FieldTimezoneMismatch,
+ FieldReportLayout: FieldReportLayout,
+ IframeWrapper,
+};
+
+});