summaryrefslogtreecommitdiff
path: root/addons/portal_rating/static/src/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/portal_rating/static/src/js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/portal_rating/static/src/js')
-rw-r--r--addons/portal_rating/static/src/js/portal_chatter.js352
-rw-r--r--addons/portal_rating/static/src/js/portal_composer.js125
-rw-r--r--addons/portal_rating/static/src/js/portal_rating_composer.js73
3 files changed, 550 insertions, 0 deletions
diff --git a/addons/portal_rating/static/src/js/portal_chatter.js b/addons/portal_rating/static/src/js/portal_chatter.js
new file mode 100644
index 00000000..fdf0bc8d
--- /dev/null
+++ b/addons/portal_rating/static/src/js/portal_chatter.js
@@ -0,0 +1,352 @@
+odoo.define('rating.portal.chatter', function (require) {
+'use strict';
+
+var core = require('web.core');
+var portalChatter = require('portal.chatter');
+var utils = require('web.utils');
+var time = require('web.time');
+
+var _t = core._t;
+var PortalChatter = portalChatter.PortalChatter;
+var qweb = core.qweb;
+
+/**
+ * PortalChatter
+ *
+ * Extends Frontend Chatter to handle rating
+ */
+PortalChatter.include({
+ events: _.extend({}, PortalChatter.prototype.events, {
+ // star based control
+ 'click .o_website_rating_select': '_onClickStarDomain',
+ 'click .o_website_rating_select_text': '_onClickStarDomainReset',
+ // publisher comments
+ 'click .o_wrating_js_publisher_comment_btn': '_onClickPublisherComment',
+ 'click .o_wrating_js_publisher_comment_edit': '_onClickPublisherComment',
+ 'click .o_wrating_js_publisher_comment_delete': '_onClickPublisherCommentDelete',
+ 'click .o_wrating_js_publisher_comment_submit': '_onClickPublisherCommentSubmit',
+ 'click .o_wrating_js_publisher_comment_cancel': '_onClickPublisherCommentCancel',
+ }),
+ xmlDependencies: (PortalChatter.prototype.xmlDependencies || [])
+ .concat([
+ '/portal_rating/static/src/xml/portal_tools.xml',
+ '/portal_rating/static/src/xml/portal_chatter.xml'
+ ]),
+
+ /**
+ * @constructor
+ */
+ init: function (parent, options) {
+ this._super.apply(this, arguments);
+ // options
+ if (!_.contains(this.options, 'display_rating')) {
+ this.options = _.defaults(this.options, {
+ 'display_rating': false,
+ 'rating_default_value': 0.0,
+ });
+ }
+ // rating card
+ this.set('rating_card_values', {});
+ this.set('rating_value', false);
+ this.on("change:rating_value", this, this._onChangeRatingDomain);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Update the messages format
+ *
+ * @param {Array<Object>} messages
+ * @returns {Array}
+ */
+ preprocessMessages: function (messages) {
+ var self = this;
+ messages = this._super.apply(this, arguments);
+ if (this.options['display_rating']) {
+ _.each(messages, function (m, i) {
+ m.rating_value = self.roundToHalf(m['rating_value']);
+ m.rating = self._preprocessCommentData(m.rating, i);
+ });
+ }
+ // save messages in the widget to process correctly the publisher comment templates
+ this.messages = messages;
+ return messages;
+ },
+ /**
+ * Round the given value with a precision of 0.5.
+ *
+ * Examples:
+ * - 1.2 --> 1.0
+ * - 1.7 --> 1.5
+ * - 1.9 --> 2.0
+ *
+ * @param {Number} value
+ * @returns Number
+ **/
+ roundToHalf: function (value) {
+ var converted = parseFloat(value); // Make sure we have a number
+ var decimal = (converted - parseInt(converted, 10));
+ decimal = Math.round(decimal * 10);
+ if (decimal === 5) {
+ return (parseInt(converted, 10) + 0.5);
+ }
+ if ((decimal < 3) || (decimal > 7)) {
+ return Math.round(converted);
+ } else {
+ return (parseInt(converted, 10) + 0.5);
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _chatterInit: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function (result) {
+ if (!result['rating_stats']) {
+ return;
+ }
+ var ratingData = {
+ 'avg': Math.round(result['rating_stats']['avg'] * 100) / 100,
+ 'percent': [],
+ };
+ _.each(_.keys(result['rating_stats']['percent']).reverse(), function (rating) {
+ ratingData['percent'].push({
+ 'num': rating,
+ 'percent': utils.round_precision(result['rating_stats']['percent'][rating], 0.01),
+ });
+ });
+ self.set('rating_card_values', ratingData);
+ });
+ },
+ /**
+ * @override
+ */
+ _messageFetchPrepareParams: function () {
+ var params = this._super.apply(this, arguments);
+ if (this.options['display_rating']) {
+ params['rating_include'] = true;
+ }
+ return params;
+ },
+
+ /**
+ * Default rating data for publisher comment qweb template
+ * @private
+ * @param {Integer} messageIndex
+ */
+ _newPublisherCommentData: function (messageIndex) {
+ return {
+ mes_index: messageIndex,
+ publisher_id: this.options.partner_id,
+ publisher_avatar: _.str.sprintf('/web/image/%s/%s/image_128/50x50', 'res.partner', this.options.partner_id),
+ publisher_name: _t("Write your comment"),
+ publisher_datetime: '',
+ publisher_comment: '',
+ };
+ },
+
+ /**
+ * preprocess the rating data comming from /website/rating/comment or the chatter_init
+ * Can be also use to have new rating data for a new publisher comment
+ * @param {JSON} rawRating
+ * @returns {JSON} the process rating data
+ */
+ _preprocessCommentData: function (rawRating, messageIndex) {
+ var ratingData = {
+ id: rawRating.id,
+ mes_index: messageIndex,
+ publisher_datetime: rawRating.publisher_datetime ? moment(time.str_to_datetime(rawRating.publisher_datetime)).format('MMMM Do YYYY, h:mm:ss a') : "",
+ publisher_comment: rawRating.publisher_comment ? rawRating.publisher_comment : '',
+ };
+
+ // split array (id, display_name) of publisher_id into publisher_id and publisher_name
+ if (rawRating.publisher_id && rawRating.publisher_id.length >= 2) {
+ ratingData.publisher_id = rawRating.publisher_id[0];
+ ratingData.publisher_name = rawRating.publisher_id[1];
+ ratingData.publisher_avatar = _.str.sprintf('/web/image/%s/%s/image_128/50x50', 'res.partner', ratingData.publisher_id);
+ }
+ var commentData = _.extend(this._newPublisherCommentData(messageIndex), ratingData);
+ return commentData;
+ },
+
+ /** ---------------
+ * Selection of elements for the publisher comment feature
+ * Only available from a source in a publisher_comment or publisher_comment_form template
+ */
+
+ _getCommentContainer: function ($source) {
+ return $source.parents(".o_wrating_publisher_container").first().find(".o_wrating_publisher_comment").first();
+ },
+
+ _getCommentButton: function ($source) {
+ return $source.parents(".o_wrating_publisher_container").first().find(".o_wrating_js_publisher_comment_btn").first();
+ },
+
+ _getCommentTextarea: function ($source) {
+ return $source.parents(".o_wrating_publisher_container").first().find(".o_portal_rating_comment_input").first();
+ },
+
+ _focusTextComment: function ($source) {
+ this._getCommentTextarea($source).focus();
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickStarDomain: function (ev) {
+ var $tr = this.$(ev.currentTarget);
+ var num = $tr.data('star');
+ if ($tr.css('opacity') === '1') {
+ this.set('rating_value', num);
+ this.$('.o_website_rating_select').css({
+ 'opacity': 0.5,
+ });
+ this.$('.o_website_rating_select_text[data-star="' + num + '"]').css({
+ 'visibility': 'visible',
+ 'opacity': 1,
+ });
+ this.$('.o_website_rating_select[data-star="' + num + '"]').css({
+ 'opacity': 1,
+ });
+ }
+ },
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickStarDomainReset: function (ev) {
+ ev.stopPropagation();
+ ev.preventDefault();
+ this.set('rating_value', false);
+ this.$('.o_website_rating_select_text').css('visibility', 'hidden');
+ this.$('.o_website_rating_select').css({
+ 'opacity': 1,
+ });
+ },
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPublisherComment: function (ev) {
+ var $source = this.$(ev.currentTarget);
+ // If the form is already present => like cancel remove the form
+ if (this._getCommentTextarea($source).length === 1) {
+ this._getCommentContainer($source).empty();
+ return;
+ }
+ var messageIndex = $source.data("mes_index");
+ var data = {is_publisher: this.options['is_user_publisher']};
+ data.rating = this._newPublisherCommentData(messageIndex);
+
+ var oldRating = this.messages[messageIndex].rating;
+ data.rating.publisher_comment = oldRating.publisher_comment ? oldRating.publisher_comment : '';
+ this._getCommentContainer($source).html($(qweb.render("portal_rating.chatter_rating_publisher_form", data)));
+ this._focusTextComment($source);
+ },
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPublisherCommentDelete: function (ev) {
+ var self = this;
+ var $source = this.$(ev.currentTarget);
+
+ var messageIndex = $source.data("mes_index");
+ var ratingId = this.messages[messageIndex].rating.id;
+
+ this._rpc({
+ route: '/website/rating/comment',
+ params: {
+ "rating_id": ratingId,
+ "publisher_comment": '' // Empty publisher comment means no comment
+ }
+ }).then(function (res) {
+ self.messages[messageIndex].rating = self._preprocessCommentData(res, messageIndex);
+ self._getCommentButton($source).removeClass("d-none");
+ self._getCommentContainer($source).empty();
+ });
+ },
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPublisherCommentSubmit: function (ev) {
+ var self = this;
+ var $source = this.$(ev.currentTarget);
+
+ var messageIndex = $source.data("mes_index");
+ var comment = this._getCommentTextarea($source).val();
+ var ratingId = this.messages[messageIndex].rating.id;
+
+ this._rpc({
+ route: '/website/rating/comment',
+ params: {
+ "rating_id": ratingId,
+ "publisher_comment": comment
+ }
+ }).then(function (res) {
+
+ // Modify the related message
+ self.messages[messageIndex].rating = self._preprocessCommentData(res, messageIndex);
+ if (self.messages[messageIndex].rating.publisher_comment !== '') {
+ // Remove the button comment if exist and render the comment
+ self._getCommentButton($source).addClass('d-none');
+ self._getCommentContainer($source).html($(qweb.render("portal_rating.chatter_rating_publisher_comment", {
+ rating: self.messages[messageIndex].rating,
+ is_publisher: self.options.is_user_publisher
+ })));
+ } else {
+ // Empty string or false considers as no comment
+ self._getCommentButton($source).removeClass("d-none");
+ self._getCommentContainer($source).empty();
+ }
+ });
+ },
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPublisherCommentCancel: function (ev) {
+ var $source = this.$(ev.currentTarget);
+ var messageIndex = $source.data("mes_index");
+
+ var comment = this.messages[messageIndex].rating.publisher_comment;
+ if (comment) {
+ var data = {
+ rating: this.messages[messageIndex].rating,
+ is_publisher: this.options.is_user_publisher
+ };
+ this._getCommentContainer($source).html($(qweb.render("portal_rating.chatter_rating_publisher_comment", data)));
+ } else {
+ this._getCommentContainer($source).empty();
+ }
+ },
+
+ /**
+ * @private
+ */
+ _onChangeRatingDomain: function () {
+ var domain = [];
+ if (this.get('rating_value')) {
+ domain = [['rating_value', '=', this.get('rating_value')]];
+ }
+ this._changeCurrentPage(1, domain);
+ },
+});
+});
diff --git a/addons/portal_rating/static/src/js/portal_composer.js b/addons/portal_rating/static/src/js/portal_composer.js
new file mode 100644
index 00000000..3524a0a8
--- /dev/null
+++ b/addons/portal_rating/static/src/js/portal_composer.js
@@ -0,0 +1,125 @@
+odoo.define('rating.portal.composer', function (require) {
+'use strict';
+
+var core = require('web.core');
+var portalComposer = require('portal.composer');
+
+var _t = core._t;
+
+var PortalComposer = portalComposer.PortalComposer;
+
+/**
+ * PortalComposer
+ *
+ * Extends Portal Composer to handle rating submission
+ */
+PortalComposer.include({
+ events: _.extend({}, PortalComposer.prototype.events, {
+ 'click .stars i': '_onClickStar',
+ 'mouseleave .stars': '_onMouseleaveStarBlock',
+ 'mousemove .stars i': '_onMoveStar',
+ 'mouseleave .stars i': '_onMoveLeaveStar',
+ }),
+
+ /**
+ * @constructor
+ */
+ init: function (parent, options) {
+ this._super.apply(this, arguments);
+
+ // apply ratio to default rating value
+ if (options.default_rating_value) {
+ options.default_rating_value = parseFloat(options.default_rating_value);
+ }
+
+ // default options
+ this.options = _.defaults(this.options, {
+ 'default_message': false,
+ 'default_message_id': false,
+ 'default_rating_value': 0.0,
+ 'force_submit_url': false,
+ });
+ // star input widget
+ this.labels = {
+ '0': "",
+ '1': _t("I hate it"),
+ '2': _t("I don't like it"),
+ '3': _t("It's okay"),
+ '4': _t("I like it"),
+ '5': _t("I love it"),
+ };
+ this.user_click = false; // user has click or not
+ this.set("star_value", this.options.default_rating_value);
+ this.on("change:star_value", this, this._onChangeStarValue);
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ // rating stars
+ self.$input = self.$('input[name="rating_value"]');
+ self.$star_list = self.$('.stars').find('i');
+
+ // set the default value to trigger the display of star widget and update the hidden input value.
+ self.set("star_value", self.options.default_rating_value);
+ self.$input.val(self.options.default_rating_value);
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onChangeStarValue: function () {
+ var val = this.get("star_value");
+ var index = Math.floor(val);
+ var decimal = val - index;
+ // reset the stars
+ this.$star_list.removeClass('fa-star fa-star-half-o').addClass('fa-star-o');
+
+ this.$('.stars').find("i:lt(" + index + ")").removeClass('fa-star-o fa-star-half-o').addClass('fa-star');
+ if (decimal) {
+ this.$('.stars').find("i:eq(" + index + ")").removeClass('fa-star-o fa-star fa-star-half-o').addClass('fa-star-half-o');
+ }
+ this.$('.rate_text .badge').text(this.labels[index]);
+ },
+ /**
+ * @private
+ */
+ _onClickStar: function (ev) {
+ var index = this.$('.stars i').index(ev.currentTarget);
+ this.set("star_value", index + 1);
+ this.user_click = true;
+ this.$input.val(this.get("star_value"));
+ },
+ /**
+ * @private
+ */
+ _onMouseleaveStarBlock: function () {
+ this.$('.rate_text').hide();
+ },
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onMoveStar: function (ev) {
+ var index = this.$('.stars i').index(ev.currentTarget);
+ this.$('.rate_text').show();
+ this.set("star_value", index + 1);
+ },
+ /**
+ * @private
+ */
+ _onMoveLeaveStar: function () {
+ if (!this.user_click) {
+ this.set("star_value", parseInt(this.$input.val()));
+ }
+ this.user_click = false;
+ },
+});
+});
diff --git a/addons/portal_rating/static/src/js/portal_rating_composer.js b/addons/portal_rating/static/src/js/portal_rating_composer.js
new file mode 100644
index 00000000..517b18aa
--- /dev/null
+++ b/addons/portal_rating/static/src/js/portal_rating_composer.js
@@ -0,0 +1,73 @@
+odoo.define('portal.rating.composer', function (require) {
+'use strict';
+
+var publicWidget = require('web.public.widget');
+var session = require('web.session');
+var portalComposer = require('portal.composer');
+
+var PortalComposer = portalComposer.PortalComposer;
+
+/**
+ * RatingPopupComposer
+ *
+ * Display the rating average with a static star widget, and open
+ * a popup with the portal composer when clicking on it.
+ **/
+var RatingPopupComposer = publicWidget.Widget.extend({
+ template: 'portal_rating.PopupComposer',
+ xmlDependencies: [
+ '/portal/static/src/xml/portal_chatter.xml',
+ '/portal_rating/static/src/xml/portal_tools.xml',
+ '/portal_rating/static/src/xml/portal_rating_composer.xml',
+ ],
+
+ init: function (parent, options) {
+ this._super.apply(this, arguments);
+ this.rating_avg = Math.round(options['ratingAvg'] * 100) / 100 || 0.0;
+ this.rating_total = options['ratingTotal'] || 0.0;
+
+ this.options = _.defaults({}, options, {
+ 'token': false,
+ 'res_model': false,
+ 'res_id': false,
+ 'pid': 0,
+ 'display_composer': options['disable_composer'] ? false : !session.is_website_user,
+ 'display_rating': true,
+ 'csrf_token': odoo.csrf_token,
+ 'user_id': session.user_id,
+ });
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ var defs = [];
+ defs.push(this._super.apply(this, arguments));
+
+ // instanciate and insert composer widget
+ this._composer = new PortalComposer(this, this.options);
+ defs.push(this._composer.replace(this.$('.o_portal_chatter_composer')));
+
+ return Promise.all(defs);
+ },
+});
+
+publicWidget.registry.RatingPopupComposer = publicWidget.Widget.extend({
+ selector: '.o_rating_popup_composer',
+
+ /**
+ * @override
+ */
+ start: function () {
+ var ratingPopupData = this.$el.data();
+ var ratingPopup = new RatingPopupComposer(this, ratingPopupData);
+ return Promise.all([
+ this._super.apply(this, arguments),
+ ratingPopup.appendTo(this.$el)
+ ]);
+ },
+});
+
+return RatingPopupComposer;
+
+});