How do I stop the 6th Star from appearing in this code? - javascript

I'm learning Javascript and as an exercise I want to take a list of text ratings and convert it into star ratings.
There are probably some easier ways and libraries to do this but I wanted to try it with my own code just so I can understand how the code works.
I've mostly stumbled my way to a nearly working solution. However, I keep getting an extra star after going through the loops.
Here is a link to fiddle with the full code...
Fiddle
This is my HTML
<div class="container" id="container">
<div class="rating" id="rating">1.2</div>
<div class="rating" id="rating">3.2</div>
<div class="rating" id="rating">4.8</div>
<div class="rating" id="rating">5</div>
<div class="rating" id="rating">2.6</div>
</div>
this is my javascript
I'm basically getting all the ".ratings" elements from the parent "container"
const ratingsContainer = document.getElementById('container');
const ratings = ratingsContainer.getElementsByClassName('rating');
Iterating through them;
In the for loop Im fetching the innerHTML of each ratings div.
const ratingScore = parseFloat(ratings[i].innerHTML).toFixed(1);
Im converting them to decimal numbers because I have to split the rating in order to get the number of full stars needed and the number of fractional stars needed.
Im also calculating the number of empty stars needed.
let emptyStars;
emptyStars = fullStars < 5 ? 5 - fullStars - 1 : 0;
I then create the required elements for the fullstars, the fractional stars and the empty stars through a loop for each that looks something like this...
for (let i = 0; i < fullStars; i++) {
const newstarContainer = document.createElement('div');
newstarContainer.className = 'starContainer';
const starFill = document.createElement('div');
starFill.className = 'starFill';
const starFillImage = document.createElement('img');
starFillImage.src = 'images/star.svg';
const starOutline = document.createElement('div');
starOutline.className = 'starOutline';
const starOutlineImage = document.createElement('img');
starOutlineImage.src = 'images/outline.svg';
const newstarContainerFill = newstarContainer
.appendChild(starFill)
.appendChild(starFillImage);
const newstarContainerOutline = newstarContainer
.appendChild(starOutline)
.appendChild(starOutlineImage);
document
.getElementById('container')
.appendChild(newRatingsContainer)
.appendChild(newstarContainer);
}
I do this for the empty and fractional stars as well.
The fractional stars get a custom width style added to the star image container this cuts off the star.svg image based on the percentage width.
const starFill = document.createElement('div');
starFill.className = 'starFill';
starFill.style.width = `${fractionalStars}%`;
Finally I append everything to the container and remove the original text ratings.
My problem is when the text ratings number is 0 or not existing, I get an extra star. I've tried to play around with the code but I cant seem to figure out why its happening.
Can someone please help me to explain what Im doing wrong.

Got a few pointers and finally came up with something that works.
The first issue was getting an extra star.
That turned out to be a problem with the part of the code that fractional and empty stars.
I was running the loop like this...
for (let i = 0; i < 1; i++) {
That meant it always run at least once. This meant that when there was no fractional star to be added like in the case of an integer without a float, the loop still run and generated a star with a width of 0%. Which looked like extra empty star. I solved that by changing the loop to an if statement since it was only supposed to run once. So the code became
if (fractionalStarsFill > 0) {
The second problem was the way I was getting the number of empty stars required. I was doing it like this...
emptyStars = fullStars < 5 ? 5 - fullStars - 1 : 0;
Which is obviously wrong. I think I was tired.
I changed that to
emptyStars = fractionalStarsFill > 0 ? 4 - fullStars : 5 - fullStars;
This takes into account whether there are fractional stars or not and generates the number of empty stars accordingly.
Finally, I refactored the code and moved the repeated statements into a function, and changed a few variables to more meaningful names.
Here is a Fiddle to the final solution.

Related

JavaScript 'score counter' to work across various pages on website

I'm trying to 'gamify' my website to engage learners. I am still incredibly new to JavaScript so I apologize in advance. There are dozens of similar articles, but none that match my needs closely and I have tried and failed to implement a fix with other similar questions - sorry (I have tried hard!)
Across approximately 10 pages, inside the navbar, I want to insert a counter. The code below is using the ID 'score-counter' to do this, and I've tried using localStorage to retain the score of that learner across each page. I've managed to get it to work (somewhat) but instead of adding 10 to the original value, it's creating an entirely new counter and starting from scratch.
I want each button to only be clickable once (per page refresh) so I have tried to remove the eventListener after each button is clicked. I also want a straight-forward way to add more buttons to the counter system, so I've tried to keep it to the two simple classes 'increment-button' and 'first-click'.
<button class="first-click increment-button">Add 10 to the value!</button>
<div id="score-counter">0</div> <!-- DISPLAYS THE COUNT -->
let score = localStorage.getItem("score") || 0;
const incrementButtons = document.querySelectorAll(".increment-button");
const scoreCounter = document.getElementById("score-counter");
scoreCounter.textContent = score;
for (let button of incrementButtons) {
button.addEventListener("click", function(){
if(this.classList.contains("first-click")) {
score += 10;
localStorage.setItem("score", score);
this.classList.remove("first-click");
}
});
}
The output currently keeps doing this: 201010101010
It's saving the output successfully, but instead of adding a value of 10 to the original score, it's creating a whole new score and starting from 0 again. Does this make sense?
I've tried changing the JavaScript to count up from the value of 0
let score = localStorage.getItem("score") || 0;
I've also tried tweaking the score value
if(this.classList.contains("first-click")) {
score = scoreCounter + 10;
I've tried looking through various StackOverflow and other web resources for similar problems but have struggled to found any answer that fits what I'm trying to achieve. Can anyone offer any advice? Thanks in advance!
You need to convert the string you get from localStorage.getItem to a number. This can be done with the unary plus operator.
let score = +localStorage.getItem("score") || 0;

What does this for-loop do?

for (o.$slider.addClass("slick-dotted"), t = i("<ul />").addClass(o.options.dotsClass), e = 0; e <= o.getDotCount(); e += 1)
t.append(i("<li />").append(o.options.customPaging.call(this, o, e)));
(o.$dots = t.appendTo(o.options.appendDots)), o.$dots.find("li").first().addClass("slick-active");
I have to modify a JS-Script from a template I downloaded. In order to do this, I first wanted to understand the script, so I know where and what I have to change. But I don't understand what these lines of code do, can anyone help?(I understand the principle of for-loops, but this one seems different and I'm a bit confused.
And yes I'm aware that you can't tell me what exactly will appear on the website, since you only have two lines of code, but I just need to know what happens with a specific class or element and I hope you can read this information from these two lines).
Thanks in advance
Strip back the object/plugin calls, add a couple of console logs:
for (console.log('do this'), console.log('then this'), e = 0; e <= 10; e += 1)
console.log.call(this, 'slider', e);
result is:
do this
then this
slider 0
slider 1
slider 2
slider 3
slider 4
slider 5
slider 6
slider 7
slider 8
slider 9
slider 10
It's building out <li>'s for the slick slider DOM
It seems like minified code, it will therefore always be hard to read and understand. You should get the unminified version and work on that instead.
Here is the same code unminified plus the rest of surrounding function (from GitHub), I hope now it makes a lot more sense:
Slick.prototype.buildDots = function() {
var _ = this,
i, dot;
if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {
_.$slider.addClass('slick-dotted');
dot = $('<ul />').addClass(_.options.dotsClass);
for (i = 0; i <= _.getDotCount(); i += 1) {
dot.append($('<li />').append(_.options.customPaging.call(this, _, i)));
}
_.$dots = dot.appendTo(_.options.appendDots);
_.$dots.find('li').first().addClass('slick-active');
}
};
It checks if the option dots is set and there are more slides than visible, and in this case it adds the class slick-dotted to the slider and creates an <ul> with the dotsClass and fills it with <li>s that contain the return value of the customPaging function. At the end, the first <li> gets the slick-active class. - I.e., it creates the dot navigation for the slides.

Best way to present editable tables

I've inherited the job of maintaining and developing an internal journaling system for registering inventory in tables on a local website. It is a website made in PHP, using jquery and handontable to list data from a MySQL database. All fields in the table are editable by the users.
Today the loading of data can be slow (10-15 seconds in the largest tables), which is mainly because of the loops used to populate the table and adjust the column sizes.
What do you think would be the best way to fix this issue? Should I reduce load times by fixing the loops, and keep handsontable as table library? Or should I scrap the old solution and implement something new?
Thanks :)
EDIT
I just saw you're using handsontable so my answer doesn't really provide a solution, as handsontable already uses a kind of list virtualization. I'll leave my answer anyway
Original Answer
What you can probably do is some sort of list virtualization, although this might be a bit tricky with table elements because you need absolute positioning and control of heights. Also it generally assumes that all rows have the same height.
The general idea is you only want to bother with rendering what's currently on the screen. Assuming you can fit 50 rows into your viewport at any time, you're measuring and updating 650 rows that don't matter. If you have 500000 rows, like in the fiddle, you're problem is going to be exponentially out of control.
Without knowing what you're doing exactly, here's a very general approach to the problem:
var elements = [];
var maxLength = 500000; // Number of elements we're going to generate
var itemHeight = 20; // We need a static row height for this to work
var totalHeight = itemHeight * maxLength; // The total height of the content
var $scrollContainer = $('#scroller-container'); // The container that will scroll
var $scrollContent = $('#scroller-contents'); // The content container for our items.
// We need to set the total height of the content so that absolute positioning works and the container receives the correctly sized scroll bar.
$scrollContent.css({ height: `${totalHeight}px` });
// Generate elements.
for (let i = 0; i < maxLength; i++) {
elements.push({
name: `item_${i}`,
value: `value_${i + 100}`
});
}
// By taking some measurements we will find out
// here exactly what items need to be rendered.
function obtainRenderableItems () {
// The size of our scrollable container
var containerHeight = $scrollContainer.height();
// How many items will fit inside the viewable area of our scrollable container
var viewport_count = Math.ceil(containerHeight / itemHeight);
// Where is it currently scrolled to.
var scrollPosition = $scrollContainer.scrollTop();
// The index of the first item in the viewable area
var start = Math.floor(scrollPosition / itemHeight);
// This calculation gives us a number of items to buffer on either side
// which prevents some janky behaviour when scrolling over yet unrendered items
var preScan = start - viewport_count <= 0 ? 0 : start - viewport_count;
// Basically we get the elements visible on the viewports by the current start
// index, and a buffer at the beginning and the end of the same amount of items
// in the viewport.
return elements.slice(preScan, preScan + (viewport_count * 3)).map((element, index) => {
return [preScan + index, element];
});
};
// Convert it to HTML, you can do whatever here, demo only.
function generateHTML (elements) {
return elements.map(el => {
let div = document.createElement('div');
div.className = 'element';
div.style.height = `${itemHeight}px`;
div.style.top = `${el[0] * itemHeight}px`;
div.innerHTML = `${el[1].name} - ${el[1].value}`;
return div.outerHTML;
}).join('');
}
// When we scroll we recalculate what items need to be shown and rerender them
// inside the page.
function onScroll (event) {
let items = obtainRenderableItems();
let htmlContent = generateHTML(items);
$scrollContent.html(htmlContent);
}
$scrollContainer.scroll(onScroll);
// Run at the beginning
onScroll();
The jQuery example above is based on a React component I wrote for exactly this purpose. You'll have to excuse my jQuery I haven't used it in years.
See the fiddle
There are a number of caveats with this approach. The major one being the row height must be the same for all rows, which is not workable for a number of situations. It also relies on a fixed container height, although the flex model can work around this.

How do I prevent my DIV element from wrapping around each of my P elements?

I'm pulling some strings out of a couple of arrays of objects and trying to layer them in div elements. Here is my code:
function renderBlogs() {
for (i = 0; i < blogArticles.length; i++) {
var currentArticle = blogArticles[i];
var divArticleWrapper = document.createElement("div");
divArticleWrapper.className = "article-wrapper";
var articleTitle = document.createElement("h1");
articleTitle.innerHTML = currentArticle.title;
var articleAuthor = document.createElement("h4");
articleAuthor.innerHTML = currentArticle.author;
var articlePublishedOn = document.createElement("h4");
articlePublishedOn.innerHTML = currentArticle.publishedOn;
var articleURL = document.createElement("a");
var articleText = document.createTextNode(currentArticle.url);
articleURL.appendChild(articleText);
articleURL.href = currentArticle.url;
divArticleWrapper.appendChild(articleTitle);
divArticleWrapper.appendChild(articleAuthor);
divArticleWrapper.appendChild(articlePublishedOn);
divArticleWrapper.appendChild(articleURL)
document.getElementById("blog-container").appendChild(divArticleWrapper);
for (j = 0; j < currentArticle.content.length; j++) {
var currentContent = currentArticle.content[j];
var divContentWrapper = document.createElement("div");
divContentWrapper.className = "content-wrapper";
var contentHeading = document.createElement("h2");
contentHeading.innerHTML = currentContent.heading;
var contentParagraph = document.createElement("p");
contentParagraph.innerHTML = currentContent.paragraph;
divContentWrapper.appendChild(contentHeading);
divContentWrapper.appendChild(contentParagraph);
divArticleWrapper.appendChild(divContentWrapper);
};
};
This works fine for "article-wrapper", but in "content-wrapper", the div wraps around each individual paragraph element instead of wrapping around all three, like so:
<div class="content-wrapper">
<h2>Slow</h2>
<p>Is changing the DOM slow? What about loading a <script> in the
<head>? JavaScript animations are slower than CSS ones, right?
Also, does a 20-millisecond operation take too long? What about 0.5
seconds? 10 seconds?</p>
</div>
<div class="content-wrapper">
<p>While it’s true that different operations take different amounts of
time to complete, it’s hard to say objectively whether something is slow
or fast without the context of when it’s happening. For example, code
running during idle time, in a touch handler or in the hot path of a game
loop each has different performance requirements. Put another way, the
people using your website or app have different performance expectations
for each of those contexts. Like every aspect of UX, we build for our
users, and what they perceive is what matters most. In fact, number one
on Google’s ten things we know to be true is “Focus on the user and all
else will follow.”</p>
</div>
<div class="content-wrapper">
<p>Asking “What does slow mean?,” then, is really the wrong question.
Instead, we need to ask “What does the user feel when they’re interacting
with the things we build?”</p>
</div>
I've tinkered around and I'm not quite sure what's wrong.
You are creating multiple divs. You need to create content-wrapper outside for and append paragraphs inside the loop.

Displaying wrong answers

I have created a spelling game where the user spells the word by clicking on letters. To show the user how many they have wrong and right I count the amount of times the right and wrong message is displayed and print it to the user.
This works perfectly for correct answers but not for wrong answers. Can someone tell me why?
This is the script for the correct answers (works fine)...
var completeLetters = $('.wordglow2').length;
var completeWords = (completeLetters / 3);
if ($(right).show()) {
$('.counter').html("Right Answers = " + completeWords).show();
}
Here is the one for incorrect answers (exactly the same logic, but won't work!)...
var incompleteLetters = $('.wordglow4').length;
var incompleteWords = (incompleteLetters / 3);
if ($(wrong).show()) {
$('.counter2').html("Wrong Answers = " + incompleteWords).show();
}
So basically "wordglow4" is the style added to the letter when it is incorrectly spelt and "wordglow2" is the style added to the correctly spelt letters.
All the words are 3 letters long, hense the "(incompleteLetters / 3)"
Here is a fiddle to help http://jsfiddle.net/smilburn/Dxxmh/34/
the 'complete words' counter works because you always leave the '.wordglow2' style on the table cells when the word is completed. Therefor $('.wordglow2').length will always return the total completed letters (and hence words)
However, the incorrect words won't work, because as soon as the user gets it right, the style is changed (from '.wordglow4' to '.wordglow2' - p.s. you might want to think about using more descriptive class names - e.g. '.correctLetter' and '.wrongLetter'). Thus, you'll never have more than 3 '.wordglow4' letters on screen, so the incompleteWords counter will never get past 1.
To make the incorrect word counter work, you'll need to declare the counter outside the function, and do something like:
var incompleteLetters = $('.wordglow4').length;
incompleteWords += (incompleteLetters / 3);
This should then keep track of previous failures, and give you the behaviour you want
There is only ever one wrong answer indicated on the board by the table cells with the class .wordglow4, unlike the correct answers which maintain the .wordglow2 class after they have been guessed correctly.
So the count of .wordglow2 will always be correct, whereas the count of .wordglow4 will only ever be 3.
You should move the count variable outside of the .click() event and increment it when a word is guessed incorrectly.
So, in your example code, add...
var animation = false;
var incompleteWords = 0; // <-- this line
$('.drag').on('click', function(e) {
and change
var incompleteWords = (incompleteLetters / 3);
to
incompleteWords += (incompleteLetters / 3);

Categories