Container Box #myRatingChangeBox
Working Example


Tip...
Small Title
Default Email
Not Logged In
Repeater #productSelector
Example Overview
This example shows how to make use of the Rating Display element in conjunction with wix Stores.
The key features are:
-
A custom data collection (ProductRatings) used to collect ratings for each product in the stores product list
-
Key column properties:
-
productId - used to store the _id of the Product being rated
-
userEmail - used to record the email of the person rating the product
-
starRating - used to record the number of stars awarded by the user rating the product
-
-
Other columns could be used
-
-
A Product data collection generated from wix Stores. This is set up and maintained using the wix My Store dashboard
-
A Repeater displaying the Product data collection with two RatingDisplay elements:
-
#myRating - used to show the rating of the currently logged in (or default) user
-
#myRatingsDisplay - used to display the average rating of all ratings in the
ProductRatings data collection for the related repeater item
-
When the page is loaded a repeater is populated after the datasets for the repeater are ready. This is controlled by the onReady functions of each dataset. The main repeater fields for Product Name, Image description etc are connected to the repeater using wix Editor binding from the dataset. The $w.Repeater.forEach() function is used to populate the rating values. As each repeater item is loaded wix-data is used to look up the ratings from the ProductRatings data collection for the current product _id. In addition a Change button is added that, when clicked (changeMyRatingBtn_click), will display a dialogue box that includes the Product item name, id and image. The dialogue box also displays two group of stars (grey for unrated, green for rated). Each star has an Id which maps to its rating value (a number from 1 to 5). When a star is clicked a generic rating function (starMouseClick) is executed. This function determines which stars need to be set to grey and which ones need to be green (see comments for the function below).
The dialogue box his hidden without change if Cancel is clicked (cancelMyRatingBtn_click). Otherwise if update is selected (updateMyRatingBtn_click) the rating is added or updated to the ProductRatings data collection.
If the email address changes or the rating value is updated the Repeater fields are updated with the new ratings by updating the $w.RatingDisplay element values.
Working Page Code
// For full API documentation, including code examples, visit http://wix.to/94BuAAs
import wixData from 'wix-data';
import wixUsers from 'wix-users';
import wixWindow from 'wix-window';
let ratings = []; // Array of all ratings available from the ProductRatings data collection
let myEmail = null; // Used to default the email used to record rating values
let mouseIn = false; // Used to prevent mouse hover flicker
$w.onReady(function () {
//Configure dynamically loaded content
loadMouseEventHandlers();
clearMyRating(1); // Clear all star values starting at position 1
setMyEmail(); // Get the current logged in user for capturing ratings
// Make sure that all data sets are loaded before we update dynamic repeater content
$w('#productDataset').onReady(() => {
$w('#productRatingsDataset').onReady(() => {
// Don't try to populate repeater items until we know that the datasets are ready for us
loadProductRatings();
});
});
});
// Function to set up the default email for this page session
function setMyEmail() {
// Use wix-users to get the current user information
let myInfo = wixUsers.currentUser;
if (myInfo.loggedIn) {
myInfo.getEmail()
.then((emailAddress) => {
myEmail = emailAddress;
$w('#defaultEmailDisplay').text = myEmail;
});
} else {
// There isn't a logged in user so set up a default. The user can change this in the rating dialogue
myEmail = 'Not Logged In';
$w('#defaultEmailDisplay').text = myEmail;
}
}
// Function that takes a product Id and uses it in conjunction with the myEmail value to search the ratings data collection
// For a rating that may already exist - we only want one rating per email.
function getMyRatingForProduct(productId) {
let promisedResult = Promise.resolve(null); // Nothing to return
if (productId && myEmail) {
// We have a productId and an Email - search for a matching record
let ratingsDataCollectionQuery = wixData.query('ProductRatings').eq('productId', productId).eq('userEmail', myEmail);
// Tee up the query promise
promisedResult = ratingsDataCollectionQuery.find()
.then ((ratingResults) => {
// Assume we didn't find a record
let queryPromisedResult = Promise.resolve(null);
console.log('ratings for productId['+productId+'] and email:['+myEmail+'] ='+ JSON.stringify(ratingResults));
if (ratingResults.totalCount === 1) {
// If we have one and only one record we have a value to return
queryPromisedResult = Promise.resolve(ratingResults.items[0].starRating);
}
// The result of this function is a promise so return what we have set up
return queryPromisedResult;
});
}
// Return the promise we have determined
return promisedResult;
}
// This function looks up all ratings in the ratings data collection for a given productId and creates an average rating for it
// The result is delivered in a promise. We return the promise so that the caller can process another .then() or a .catch() based on any
// Query issues. The result is sufficient to configure a $w.RatingsDisplay element
function getRatingForProduct(productId) {
// Set up and return the wixData query
return wixData.query('ProductRatings').eq('productId', productId).find()
.then ((ratingList) => {
// Initialise our calculation variables
let ratingCumulative = 0;
let ratingCount = ratingList.totalCount;
// Cycle through each result in the returned items list and calculate the rating
ratingList.items.forEach((ratingRecord) => {
ratingCumulative += ratingRecord.starRating;
});
// Calculate the average rating
let ratingsAverage = (ratingCount > 0 ? ratingCumulative/ratingCount : 0);
// Return the result object in a promise
return Promise.resolve({'average':ratingsAverage, 'totalCounted':ratingCount});
})
}
// Given a context selector and product Id this function will set up the rating information for a repeater Item
function setRatingForProduct($selector, productId) {
return getRatingForProduct(productId)
.then((ratingResult) => {
// If we have a set of ratings then configure the ratingsDisplay accordingly
if (ratingResult.totalCounted > 0) {
$selector('#ratingsDisplay').numRatings = ratingResult.totalCounted;
$selector('#ratingsDisplay').rating = ratingResult.average;
} else {
// No ratings
$selector('#ratingsDisplay').numRatings = 0;
// This method of resetting the RatingsDisplay using 'undefined' currently throws a wix SDK warning
// which has been reported to Wix
$selector('#ratingsDisplay').rating = undefined;
}
});
}
// *************************
// ** Convenience functions
// *************************
// Set the stars in the My Rating dialogue box to the unset grey colored state
function clearMyRating(start) {
for (var i = (start<1?1:start); i <= 5; i++) {
setGrey(i);
}
}
// Color the rating stars green that satisfy the rating value parameter
function setMyRating(value) {
clearMyRating(value); //set unused to grey
for (var i = 1; i <= value; i++) {
setGreen(i);
}
}
// Simple function to show a green star and hide its grey counterpart
// Each element is named the same with a numeric suffix for the star rating value
// There are fice of each as follows:
// $w('#greyStar1'), $w('#greyStar2'), $w('#greyStar3'), $w('#greyStar4'), $w('#greyStar5')
// $w('#greenStar1') ,$w('#greenStar2') ,$w('#greenStar3') ,$w('#greenStar4') ,$w('#greenStar5')
function setGreen(index) {
$w('#greyStar'+index.toString()).hide();
$w('#greenStar'+index.toString()).show();
}
// Simple function to show a grey star and hide its green counterpart
// Each element is named the same with a numeric suffix for the star rating value
// There are fice of each as follows:
// $w('#greyStar1'), $w('#greyStar2'), $w('#greyStar3'), $w('#greyStar4'), $w('#greyStar5')
// $w('#greenStar1') ,$w('#greenStar2') ,$w('#greenStar3') ,$w('#greenStar4') ,$w('#greenStar5')
function setGrey(index) {
$w('#greyStar'+index.toString()).show();
$w('#greenStar'+index.toString()).hide();
}
// Function to detect a change to the email input text box and update the default email variable as needed
export function emailAddress_change(event) {
// Set email address
myEmail = $w('#emailAddress').value;
$w('#defaultEmailDisplay').text = myEmail;
loadProductRatings();
getMyRatingForProduct($w('#productId').text)
.then((rating) => {
setMyRating(rating);
});
}
function loadProductRatings() {
// Reload the repeater by reloading the current page
$w('#productSelector').forEachItem(($itemSelector, itemData, index) => {
updateProductRating($itemSelector, itemData);
});
}
function updateProductRating($itemSelector, itemData) {
// Calculate the ratings for this $itemSelector context using the itemData record
setRatingForProduct($itemSelector, itemData._id);
// Use 'itemData' to find any existing rating that I may have given to the product
getMyRatingForProduct(itemData._id)
.then((rating) => {
// If I have rated this product then there will be a value returned otherwise this will be null
$itemSelector('#myRating').rating = rating;
});
}
// Function to process the change rating repeater button "Change"
export function changeMyRatingBtn_click(event, $w) {
let selector = $w.at(event.context); // Get the selector we need from the repeater's event context
// Using the selector for this repeater element get the related product record
let thisProduct = selector('#productDataset').getCurrentItem();
// Get my rating to display in the My Rating dislogue
getMyRatingForProduct(thisProduct._id)
.then((rating) => {
// Enable the rating change dialog
activateChangeDialog(selector, rating);
})
.catch((err) => {
showError('ERROR: '+err.message);
});
}
// Function displays the 'My Rating' dialogue to change the rating for a given product
function activateChangeDialog(selector, rating) {
// Get the product record for this change request
let productRecord = selector('#productDataset').getCurrentItem();
// Disable Change button
selector('#changeMyRatingBtn').disable();
// Update the product name in the 'My Rating' dialogue.
$w('#productName').text = productRecord.name;
// Update the product id in the 'My Rating' dialogue.
$w('#productId').text = productRecord._id;
// Update the product image in the 'My Rating' dialogue.
$w('#productImage').src = productRecord.mainMedia;
// Update the product rating in the 'My Rating' dialogue from my rating
setMyRating(rating);
// Set he email address related to this rating
$w('#emailAddress').value = (myEmail && myEmail !== 'Not Logged In'?myEmail:null);
// Show the change dialog
$w('#myRatingChangeBox').show();
$w('#ratingChangeBoxAnnotation').show();
}
// Loads an error message in red when called
function showError(error) {
$w('#errorText').text = error;
$w('#errorText').show();
}
// Hides the error message
function clearError () {
$w('#errorText').hide();
}
// Function called when the update button in the My Ratings dialogue is clicked.
// This will update the record for the selected product using the email address in the input box.
// NOTE: if a new email address is added to the dialogue box the default email address used in this example will be changed.
export function updateMyRatingBtn_click(event, $w) {
// Get the new ratings
let rating = getStarRating();
// Use exception handling to validate the new information
if (rating === 0) {
// No rating was selected
showError("ERROR: Must have a rating!");
} else if (!myEmail || myEmail === 'Not Logged In' ) {
// No email is available need to add one
showError("ERROR: You must enter an email to add a rating");
} else {
// Save the new rating value and then reset the change buttons
let productId = $w('#productId').text;
saveProductRating(productId, rating, myEmail)
.then((savedRecord) => {
// Hide the 'My Rating' dialogue on sucess!
$w('#myRatingChangeBox').hide();
$w('#ratingChangeBoxAnnotation').hide();
// Re enable the repeater change button by walking the repeater looking for disabled buttons and enable them
$w('#productSelector').forEachItem(($item, itemData, index) => {
if (itemData._id === $w('#productId').text || !$item('#changeMyRatingBtn').enabled) {
$item('#changeMyRatingBtn').enable();
}
// Update the repeater rating content if the itemData matches the productId
if (productId === itemData._id) {
updateProductRating($item, itemData);
}
});
})
.catch((err) => {
showError(err.message);
});
}
}
// Save a newly created rating.
// This function will first chjeck that the product ratings datacollection doesn't have a record for this product from this emailid
// If it does it will change this value.
// If it doesn't a new record will be created and added to the data collection
function saveProductRating(productId, rating, email) {
return wixData.query('ProductRatings').eq('productId', productId).eq('userEmail', email).find()
.then((results) => {
let itemToSave = null; // record to save in our data collection
// Cannot have more than one rating per product per email
if (results.totalCount > 1) {
throw Error('too many ratings for product ['+productId+'] from user ['+email+']');
}
// We have a record or need a new one
if (results.totalCount === 0) {
// Need a new one
itemToSave = {};
itemToSave.productId = productId;
itemToSave.userEmail = email;
} else {
// Re use the found record - it will have a record Id which will prevent duplication upon save.
itemToSave = results.items[0];
}
itemToSave.starRating = rating;
return wixData.save('ProductRatings', itemToSave);
})
.catch((error) => {
console.log('bubbling up error from wixData: '+error.message);
throw error;
});
}
// Convenience function that creates onClick handlers for all 10 colored star elements in My Ratings
function loadMouseEventHandlers() {
for (var i = 1; i <= 5; i++) {
$w("#greyStar"+i.toString()).onClick(starMouseClick);
$w("#greenStar"+i.toString()).onClick(starMouseClick);
}
}
// Function to handle clicks from colored rating stars
// The function determines the color of the star and its index and adjusts the rating view accordingly.
// Clicking on a green star will set that star and its successor stars to grey
// Clicking on a grey star will set that star and all of its predecessor stars to green
function starMouseClick(event) {
// Get element name
let starId = '#'+event.target.id;
let starColorIsGreen = (starId.includes('green') ? true : false);
// Get index of clicked on star. Adjust if star is green because we will make this grey!
let starRatingNumber = parseInt(starId.substring(starId.length-1), 10) - (starColorIsGreen?1:0);
// Cycle through the star elements up to starRatingNumber setting them to green
for (var i = 1; i <= starRatingNumber; i++) {
setGreen(i);
}
// Remaining stars should be grey
for (;i <= 5; i++) {
setGrey(i);
}
}
// Function to get the star rating from the individual stars
// Each star has a color and a rating number from 1 to 5
// The rating stars are green we need the highest index value for a green star.
// We walk backwards through the green stars looking for one that is not hidden. That will be our rating.
function getStarRating() {
// Walk the stars to find out which ones have been set
let result = 0;
for (var i = 5; i > 0 ; i--) {
if (!$w('#greenStar'+i.toString()).hidden) {
result = i;
break; // Exit the loop
}
}
return result;
}
// ******************************************************************************************************************
// Helper functions used to show information on the example page. These functions are not part of the active example.
// ******************************************************************************************************************
export function productDataSetBtn_click(event, $w) {
// Load lightbox to show data collection contents
wixWindow.openLightbox('DataCollectionDisplay', {'tableName':'Stores/Products'});
}
export function productRatingsDatasetBtn_click(event, $w) {
// Load lightbox to show data collection contents
wixWindow.openLightbox('DataCollectionDisplay', {'tableName':'ProductRatings'});
}
export function productRatingsDatasetBtn_mouseIn(event, $w) {
//Show tool tip for this button
showToolTip("Click to show Product Ratings Data Collection");
}
export function productRatingsDatasetBtn_mouseOut(event, $w) {
//Remove tool tip
hideToolTip();
}
export function productDataSetBtn_mouseIn(event, $w) {
//Show tool tip
showToolTip("Click to show Products Data Collection")
}
export function productDataSetBtn_mouseOut(event, $w) {
//Hide tool tip
hideToolTip();
}
function showToolTip(tip) {
$w('#toolTipText').text = tip;
$w('#toolTip').show();
}
function hideToolTip() {
$w('#toolTip').hide();
}
export function cancelMyRatingBtn_click(event, $w) {
// Hide ratings Change Box
$w('#myRatingChangeBox').hide();
// Reset change buttons on repeaters
$w('#productSelector').forEachItem(($item, itemData, index) => {
if (itemData._id === $w('#productId').text || !$item('#changeMyRatingBtn').enabled) {
$item('#changeMyRatingBtn').enable();
}
});
}
Container Box #myRatingChangeBox
My Rating
Small Title
Small Title

Text #productName
Input #emailAddress