I'm trying to build a reoccurring logic using jQuery & Arrays, but running into issues with getting the code to rerun. I'd like the code to read the next array within the matrix once the user clicks "Next Button." Currently, the logic isn't progressing past the first array, but I'm not sure why! Any help is appreciated.
<body>
<div id="wordZone"></div>
<ul id="choices">
<li></li>
<li></li>
<li></li>
</ul>
Next Word
<div class="win">
You've Won!
</div>
<div class="lose">
You've Lost!
</div>
</body>
let score = 0;
const dict = {
officeSpeak: ["Hi there!", "Regards", "Per my last email"],
counter: 0,
};
const matrix = [
["Hi there!", "Sup dude", "Salutations"],
["Regards", "Hasta luego", "Byebye"],
["Per my last email","oopsie!","some other option here, you're writing this game"],
];
const wordZone = $("#wordZone");
const choiceButtons = $("#choices li");
function buildOptions() {
let turnChoices = matrix[0];
//hide next word button - DONE
$("#next").hide();
for (let i = 0, ii = turnChoices.length; i < ii; i++) {
let choiceWord = turnChoices[i];
let choiceButton = $(choiceButtons[i]);
let btnClass = "incorrect";
choiceButton.text(choiceWord);
if (dict.officeSpeak.indexOf(choiceWord) != -1) {
btnClass = "correct";
}
choiceButton.addClass(btnClass);
}
}
buildOptions();
function onClickWord(e) {
console.log($(this));
$("#choices li").addClass("active");
$(".correct").css("color", "green");
$(".incorrect").css("color", "red");
if ($(this).hasClass("correct")) {
score++;
console.log(score);
}
$("#next").show();
let turnChoices = matrix[+1];
}
$("#choices li").click(onClickWord);
$("#next").click(buildOptions);
function finalScore() {
$("#wordZone").show(score);
if (finalScore >= 2) {
$("#wordZone").addClass("win");
$("#win").show();
} else {
$("#wordZone").addClass("lose");
$("#lose").show();
}
}
finalScore();
//final score - HELP
I tried creating a for loop where the matrix counter should increment by 1 each time the program is ran, expecting that the code would then move onto the second line of the array.
It tooks a while to find a running way. Here my suggestion:
First: create a variable with global scope
let matrixcounter = 0;
Second: Add an argument to function buildOptions and pass it to your array matrix[]:
function buildOptions(wordCounter) {
let turnChoices = matrix[wordCounter];
...
}
This last change needs another important change, based on How can I pass arguments to event handlers in jQuery? :
So replace $("#next").click(buildOptions); with
$("#next").click(function() {
matrixcounter++; //means matrixcounter = matrixcounter + 1;
buildOptions(matrixcounter);
});
A running example: https://jsfiddle.net/reporter/rtqgveuo/1/
Related
I'm working on building a quiz using Jquery, Loops, and Arrays. I started with looping through the answers (seen in matrix). Now I'd like to do the same thing for questions so that each of the three options appears with a question/prompt as well. The matrix matches the dict object I have above, so I'm not sure how to replicate for questions...
Secondly, I'm having trouble getting a final score to show once we've reached the end of the arrays in the matrix. I've tried setting it so that the final score button appears once the matrix counter has reached an index beyond number of arrays, but it seems to be causing some issues. I also am having trouble returning the actual final score on the screen.
Any advice is helpful!
<body>
<div id="Start">
<div id="welcome" class="question">
<p>
Choose the right response. Don't get fired.
</p>
</div>
<button type="button" class="startBtn" id="startBtn">Start</button>
</div>
<div id="wordZone">
</div>
<ul id="choices">
<li></li>
<li></li>
<li></li>
</ul>
Next Word
Final Score
<div id="finalScore" class="hide">
<h3 id="scoreMessage"></h3>
<p id="playerScore"></p>
</div>
</body>
</html>
// setup
let score = 0;
let matrixcounter = 0;
const dict = {
officeSpeak: ["Hi there!", "Regards", "Per my last email"],
counter: 0,
};
const matrix = [
["Hi there!", "Sup dude", "Salutations"],
["Regards", "Hasta luego", "Byebye"],
[
"Per my last email",
"oopsie!",
"some other option here, you're writing this game",
],
];
const wordZone = $("#wordZone");
const choiceButtons = $("#choices li");
$("#choices").hide();
$("#end").hide();
$("#startBtn").click(function () {
$("#choices").show();
});
function buildOptions(wordCounter) {
let turnChoices = matrix[wordCounter];
$("#next").hide();
for (let i = 0, ii = turnChoices.length; i < ii; i++) {
let choiceWord = turnChoices[i];
let choiceButton = $(choiceButtons[i]);
let btnClass = "incorrect";
choiceButton.text(choiceWord);
if (dict.officeSpeak.indexOf(choiceWord) != -1) {
btnClass = "correct";
}
choiceButton.addClass(btnClass);
}
}
buildOptions(matrixcounter);
function onClickWord(e) {
console.log($(this));
$("#choices li").addClass("active");
$(".correct").css("color", "green");
$(".incorrect").css("color", "red");
if ($(this).hasClass("correct")) {
score++;
console.log(score);
}
$("#next").show();
let turnChoices = matrix[+1];
}
$("#choices li").click(onClickWord);
$("#next").click(function () {
matrixcounter++;
buildOptions(matrixcounter);
$(".correct").css("color", "black");
$(".incorrect").css("color", "black");
});
function finalScore() {
if (matrixcounter >= buildOptions(turnChoices.length)) {
$("#end").show();
}
$("#end").click(function () {
return score;
});
// let finalScore = score;
// $("#wordZone").show(finalScore);
// if (finalScore >= 2) {
// $("#wordZone").addClass("win");
// $("#win").show();
// } else {
// $("#wordZone").addClass("lose");
// $("#lose").show();
// }
}
finalScore();
I tried setting up questions as such:
const questions = [{ question: "Did you get the email I sent 5 minutes ago? Havent heard from u.", choices: ["I'm busy", "You just sent it", "Havent had a chance to look!",], correctAnswer: "Havent had a chance to look!" }, {
however it broke the loop I had previously set up to have matrix read dict as an answer key.
I'm trying to check every individual value in array 'chkArr' to check if that string occurs in another array 'arr' and if it does return blank "". I have it working for the first value "text" but it isn't looping though the other element of chkArr the way I expected.
If I manually change the loop variable 'rep' I get the correct result for whichever array element is selected. Can anyone see what I'm doing wrong? Perhaps I need to move the loop out of the filterLogic function?
function myFunction() {
var arr = [["random.text1"],[6.0],["othermsg"],[8],["testtext2"]];
var newArr = arr.map(filterLogic);
Logger.log(newArr);
}
var filterLogic = function(item){
var chkArr = [["text"],[6.0],["other"]["other"]];
for (var rep = 0; rep < chkArr.length; rep++) {
if(item.toString().indexOf(chkArr[rep]) === -1){return item;} else {return [""];}
}
}
So the result I would hope to get from the above would be:
[[], [], [], [8.0], []]
However what I actually get is:
[[], [6.0], [othermsg], [8.0], []]
I found a way to do it which does give me the result I need. I'm sure there is a better way still to do this so if anyone has a neater method please share.
My solution.
function myFunction() {
var arr = [["random.text1"],[6.0],["othermsg"],[8],["testtext2"]];
rep = 0;
for (rep = 0; rep <= 2; rep++){
var newArr = arr.map(filterLogic);
Logger.log(newArr);
}
}
var filterLogic = function(item){
var chkArr = [["text"],[6],["other"]];
if(item.toString().indexOf(chkArr[rep]) === -1){return item;} else {return [""];}
}
I'm trying to create a google apps script that will format certain parts of a paragraph. For example, text that is underlined will become bolded/italicized as well.
One docs add-on I have tried has a similar feature: https://imgur.com/a/5Cw6Irn (this is exactly what I'm trying to achieve)
How can I write a function that will select a certain type of text and format it?
**I managed to write a script that iterates through every single letter in a paragraph and checks if it's underlined, but it becomes extremely slow as the paragraph gets longer, so I'm looking for a faster solution.
function textUnderline() {
var selectedText = DocumentApp.getActiveDocument().getSelection();
if(selectedText) {
var elements = selectedText.getRangeElements();
for (var index = 0; index < elements.length; index++) {
var element = elements[index];
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
var textLength = text.getText().length;
//For every single character, check if it's underlined and then format it
for (var i = 0; i < textLength; i++) {
if(text.isUnderline(i)) {
text.setBold(i, i, true);
text.setBackgroundColor(i,i,'#ffff00');
} else {
text.setFontSize(i, i, 8);
}
}
}
}
}
}
Use getTextAttributeIndices:
There is no need to check each character in the selection. You can use getTextAttributeIndices() to get the indices in which the text formatting changes. This method:
Retrieves the set of text indices that correspond to the start of distinct text formatting runs.
You just need to iterate through these indices (that is, check the indices in which text formatting changes), which are a small fraction of all character indices. This will greatly increase efficiency.
Code sample:
function textUnderline() {
var selectedText = DocumentApp.getActiveDocument().getSelection();
if(selectedText) {
var elements = selectedText.getRangeElements();
for (var index = 0; index < elements.length; index++) {
var element = elements[index];
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
var textRunIndices = text.getTextAttributeIndices();
var textLength = text.getText().length;
for (let i = 0; i < textRunIndices.length; i++) {
const startOffset = textRunIndices[i];
const endOffset = i + 1 < textRunIndices.length ? textRunIndices[i + 1] - 1 : textLength - 1;
if (text.isUnderline(textRunIndices[i])) {
text.setBold(startOffset, endOffset, true);
text.setBackgroundColor(startOffset, endOffset,'#ffff00');
} else {
text.setFontSize(startOffset, endOffset, 8);
}
}
}
}
}
}
Reference:
getTextAttributeIndices()
Based on the example shown in the animated gif, it seems your procedure needs to
handle a selection
set properties if the selected region is of some format (e.g. underlined)
set properties if the selected region is NOT of some format (e.g. not underlined)
finish as fast as possible
and your example code achieves all these goals expect the last one.
The problem is that you are calling the text.set...() functions at each index position. Each call is synchronous and blocks the code until the document is updated, thus your run time grows linearly with each character in the selection.
My suggestion is to build up a collection of subranges from the selection range and then for each subrange use text.set...(subrange.start, subrange.end) to apply the formatting. Now the run time will be dependent on chunks of characters, rather than single characters. i.e., you will only update when the formatting switches back and forth from, in your example, underlined to not underlined.
Here is some example code that implements this subrange idea. I separated the specific predicate function (text.isUnderline) and specific formatting effects into their own functions so as to separate the general idea from the specific implementation.
// run this function with selection
function transformUnderlinedToBoldAndYellow() {
transformSelection("isUnderline", boldYellowOrSmall);
}
function transformSelection(stylePredicateKey, stylingFunction) {
const selectedText = DocumentApp.getActiveDocument().getSelection();
if (!selectedText) return;
const getStyledSubRanges = makeStyledSubRangeReducer(stylePredicateKey);
selectedText.getRangeElements()
.reduce(getStyledSubRanges, [])
.forEach(stylingFunction);
}
function makeStyledSubRangeReducer(stylePredicateKey) {
return function(ranges, rangeElement) {
const {text, start, end} = unwrapRangeElement(rangeElement);
if (start >= end) return ranges; // filter out empty selections
const range = {
text, start, end,
styled: [], notStyled: [] // we will extend our range with subranges
};
const getKey = (isStyled) => isStyled ? "styled" : "notStyled";
let currentKey = getKey(text[stylePredicateKey](start));
range[currentKey].unshift({start: start});
for (let index = start + 1; index <= end; ++index) {
const isStyled = text[stylePredicateKey](index);
if (getKey(isStyled) !== currentKey) { // we are switching styles
range[currentKey][0].end = index - 1; // note end of this style
currentKey = getKey(isStyled);
range[currentKey].unshift({start: index}); // start new style range
}
}
ranges.push(range);
return ranges;
}
}
// a helper function to unwrap a range selection, deals with isPartial,
// maps RangeElement => {text, start, end}
function unwrapRangeElement(rangeElement) {
const isPartial = rangeElement.isPartial();
const text = rangeElement.getElement().asText();
return {
text: text,
start: isPartial
? rangeElement.getStartOffset()
: 0,
end: isPartial
? rangeElement.getEndOffsetInclusive()
: text.getText().length - 1
};
}
// apply specific formatting to satisfy the example
function boldYellowOrSmall(range) {
const {text, start, end, styled, notStyled} = range;
styled.forEach(function setTextBoldAndYellow(range) {
text.setBold(range.start, range.end || end, true);
text.setBackgroundColor(range.start, range.end || end, '#ffff00');
});
notStyled.forEach(function setTextSmall(range) {
text.setFontSize(range.start, range.end || end, 8);
});
}
I was trying to create a counter using generators where user will click on the button and the count value will be increased. When i tried to get the function i am not getting that one. Am i missing something .
Thanks in Advance
index.html
<div class="counter">
<div id="count-gen">0</div>
<button onclick="countGen()">Count</button>
</div>
index.js
function* countGen(){
let count =0;
let nextValue = yield count++
document.getElementById('count-gen').innerHTML = nextValue
}
You've conflated two things which should be separate: Your generator, and the event handler for using your generator. You also need to be using the generator's iterator:
The generator (note the loop):
function* countGen() {
let count = 0;
while (true) {
yield ++count;
}
}
(I also made it a pre-increment as you appeared to want to start at 1.)
Getting the iterator:
const count = countGen();
Using the iterator:
function handler() {
document.getElementById('count-gen').innerHTML = count.next().value;
}
Live Example:
function* countGen() {
let count = 0;
while (true) {
yield ++count;
}
}
const count = countGen();
function handler() {
document.getElementById('count-gen').innerHTML = count.next().value;
}
document.querySelector("button").addEventListener("click", handler);
<div class="counter">
<div id="count-gen">0</div>
<button>Count</button>
</div>
(The example also uses addEventListener rathern than an onxyz-attribute handler.)
So basically I would like to create a function that when alerted, returns the URL from an array (in this case the array is declared as 'websites'). The function has two parameters 'websites' and 'searchTerm'.
I'm struggling to make the function behave, so that when i type yahoo or google or bing in the searchTerm parameter for the function; I want it to return the corresponding URL.
Any help or support would be greatly appreciated.
Sorry if I have not made myself clear in my explanation, if this is the case, let me know and I will try and be clearer in my explanation.
Thanks in advance!
Try something more like:
var websites = {google: 'www.google.com', yahoo: 'www.yahoo.com'};
function filterURL(websites,searchTerm)
{
return websites[searchTerm] || 'www.defaultsearchwebstirehere.com';
}
** Update following comment **
Build up your websites object like so (where input is your array of key values seperated by pipe characters):
var websites = {};
for (var i = 0; i < input.length; i++) {
var siteToSearchTerm = input[i].split('|');
websites[siteToSearchTerm[1]] = siteToSearchTerm[0];
}
Here is how:
var websites = ["www.google.com|Google" , "www.yahoo.com|Yahoo" , "www.bing.com|Bing"];
function filterURL(websites,searchTerm)
{
for (var i = 0; i < websites.length; i++) {
if (websites[i].split('|')[1] === searchTerm) {
return websites[i].split('|')[0];
}
}
}
Working Example
You can also validate and improve function:
function filterURL(websites,searchTerm)
{
if (typeof websites != 'Array' || ! searchTerm) return false;
for (var i = 0; i < websites.length; i++) {
if (websites[i].split('|')[1] === searchTerm) {
return websites[i].split('|')[0];
}
}
return false;
}
Why not just use an object?
var websites = {
Google: 'www.google.com',
Yahoo: 'www.yahoo.com'
};
function filterURL(sites, searchTerm) {
if (sites[searchTerm]) {
return sites[searchTerm];
} else {
// What do you want to do when it can't be found?
}
}
alert(filterURL(websites, 'Google')); // alerts 'www.google.com'
You should really be using a hash-table like structure so that you don't have to search through the whole array every time. Something like this:
var websites = {
"Google": "www.google.com",
"Yahoo": "www.yahoo.com",
"Bing": "www.bing.com"
};
function filterURL(websites, searchTerm) {
if (websites[searchTerm] !== undefined)
return websites[searchTerm];
else
return null;
}
I'm not sure why you want to use an array for this, as what you're really doing fits a key-value pair better; however, here's how I'd do it:
function filterURL(websites, searchTerm) {
var i = 0,
parts;
for (i = 0; i < websites.length; i++) {
parts = websites[i].split("|");
if (parts[1].toLowerCase() === searchTerm) {
return parts[0];
}
}
}
But consider if you used a proper JavaScript Object instead:
var websites = {
Google: "www.google.com",
Yahoo: "www.yahoo.com",
Bing: "www.bing.com"
}
// Now it's much simpler:
function filterURL(websites, searchTerm) {
// key has first letter capitalized…
return websites[searchTerm.charAt(0).toUpperCase() + searchTerm.slice(1).toLowerCase()];
}