onkeydown, and auto complete - javascript

I was wondering if anyone could help me solve this issue or point me towards the right direction.
In my project we have a filed that needs to be autofilled, at this moment I use onblur which works wonders as it only does it so once you leave the focus. However, due to recent changes, it needs to only do so when there is only one unique item in the map which it matches the input.
I have a large array defined as following:
var myArray = [
[content, content],
[content, content],
...
]
Later in my code I associate it with a map, at least this is what most stackoverflow questions I looked at referred to it as follows:
var myMap = {};
for(0 to myArray.length) {
var a = myArray[i][0];
var b = myArray[i][1];
myMap[a] = b;
}
Now, finally I iterate over this array as follows:
for (var key in map) {
if (map.hasOwnProperty(key)) {
if (map[key].toLowerCase().indexOf(location.toLowerCase()) >= 0)
the above is the line of code I am struggling to figure out how to change. At this moment, while using on blur, if I type in the letter 'A' for example, and leave the focus area it will automatically fill it in with a certain name. However, in the array there are many other objects that begin with, or contain A. How can I change it so that the onkeydown event will keep going until it finally filters it down to to only possible key-value pair? I tried looking at MDN's documentation for filtering, but I do not think that will work for my purposes, or at least I am too inexperienced with JS.

If the indexOf the first and last are nonnegative and equal, there is just one. You could do this with an && and boolean short circuit evaluation, but that will run very far right off the screen, so I am showing your code with one more nested if (up to you to add the end of the block). But we also need to see if there are matches on multiple keys.
var matchCount=0;
for (var key in map) {
if (map.hasOwnProperty(key)) {
if (map[key].toLowerCase().indexOf(location.toLowerCase()) >= 0){
if (map[key].toLowerCase().indexOf(location.toLowerCase()) == map[key].toLowerCase().lastIndexOf(location.toLowerCase())) {
matchCount++;
then outside your for loop:
if (matchCount==1){ //do your stuff

Related

For Loop in JavaScript is not working correctly

const box = document.querySelectorAll(".box");
console.log(box.length);
const direction = [
"top-start",
"bottom-start",
"left-start",
"right-start",
"top-center",
"bottom-center",
"left-center",
"right-center",
"top-end",
"bottom-end",
"left-end",
"right-end"
];
box.forEach((el, index) => {
for (let i = 0; i < direction.length; i++) {
CreateTooltip(el, direction[index], "Hello, World!");
}
});
The above mentioned code rendering 144 tooltips in DOM and I want only 12 with each should have different directions. I don't why this loop is not working! I tried to add forEach loop inside for loop but still the problem is same.
NOTE As some of you asked I pasted my entire code. Hope it will help you and then you will help me. 😅
You can pass in the index of each element like this and get the corresponding value from the direction array
box.forEach((element, index) => {
CreateTooltip(element, direction[index], 'Hello, World!');
})
Your code is working well, as you are iterating over list of boxes and inside that you have another iteration, so the result of your code will always be (number of boxes)*(number of directions) = 144..
So you can Iterate only on boxes or on direction by manipulating one and other lists by there index numbers as given below..
$(box).each(function(i,v){console.log(direction[i])})
If you are using Queryselectorall , if it is class use dot , or if you are using id use #
const box = document.querySelectorAll('.box');
From what you have stated, namely wanting 12 tooltips (same as direction index counts) but getting 144 tooltips (144/12 = 12) you should have 12 elements with .box class in your page. the problem is in your query selection.
const box = document.querySelectorAll('.box');
console.log(box.length); // this should show 12
what you need to do is to set an ID for your element or create a loop for the box (array at this point) and execute your CreateTooltip logic for each of them separately.
Note: I suggest you to test your CreateTooltip in a testing environment (separated file created to test your code) with only one .box element, it's possible that the library you're using does not support multiple calls of CreateTooltip for a single element, that might be the reason you're getting 144 elements instead of 12.

Native JS alternative for Java's next() and hasNext() methods

So recently I built a search and replace program with Java, now I am working on translating/rebuilding that program with JavaScript. However, I am having trouble finding JS method alternatives for next() and hasNext(). I am new to JS so I don't know what JS methods would work similarly to the Java methods I am used to.
This is my program, I commented through it to show exactly what I am doing with the previously mentioned methods. Basic set up, 2 text areas, one for the search box (search criteria, box 2), and one for the main document (the field of search, box 1). It basically boils down to a cross-reference. It will highlight all the similarities between the documents.
function search() {
//define an array to store the search criteria.
var array = [];
// define a counter.
var n = 0;
// define a constant for the first box, the search field.
const box1 = document.getElementById("box1");
// define a constant for the second box, the search criteria.
const box2 = document.getElementById("box2");
// loop through the search criteria, storing each word as a seperate element in the array.
// this uses non js terms, this is where I need the help.
while (box2.hasNext()) {
array[n] = box2.next();
n = n + 1;
}
// resets the counter.
n = 0;
// loops through each search item, finding and replacing each item with itself, surrounded by mark tags.
while (n <= array.length) {
box1.replace(array[n], "<mark>" + array[n] + "</mark>");
}
}
</script>
There is bound to be other issues, bugs and syntax, feel free to point them out but lets try and keep the focus on the methodology (i.e. method alternatives for next() and hasNext()).
Thanks.
-EDIT- I'd prefer to use native alternative (no jquery) becuase I know even less about jquery than I do js.

Creating arrays on condition in javascript

This has been eating me away. Check CodePen here. I have a function that adds elements to a ruler. Call expose.init(lengthOfPeriod) with an integer to set the length of each period in the ruler. Call expose.addAction(time, team) with an integer and a string == 'HOME' || 'AWAY' to place an action in the ruler.
When the screen is resized, I want something to happen to the elements in the ruler that touch each other (basically collapse into a group).
I have a function 'detectOverlap' that takes 2 params and determines if they are touching or not. I also have a function in the resize event handler that populates an array 'allTouching' with all the elements in the ruler that are touching each other at each resize.
if (actionArray.length > 1) { //don't run if there's only one element in the page
actionArray.reduce(function (prev, next) {
if (detectOverlap(prev, next)) {
if (allTouching.indexOf(prev, next) === -1) allTouching.push(prev, next);
}
return next;
});
If actions are touching each other, I need them to collapse into groups. In order to do this, I need to create an array for each group of actions touching each other. However, I haven't been able to make this happen so far.
Here's some pseudo code:
for (i = 0; i < allTouching.length; i++) {
if (detectOverlap(allTouching[0], alltouching) {
touchingGroup[i] = new Array(allTouching[0], all other elements touched by 0);
do not include any element more than once in touchingGroup[i];
do not include any similar arrays (same elements) in allGroups;
allGroups.push(touchingGroup[i]);
}
}
In short, this would need to loop for all the elements in the allTouching array, and create a new touchingGroup[n] for each new group of actions that touch each other.
This sounds simple in my head, and I'm sure there must be a way to do it without code getting overly complex, but I haven't found it yet.
Any feedback would be appreciated.
It seems your question is only about the grouping, so I will ignore the visualisation aspect and assume that the function detectOverlap is correct.
Then you could create the groups in one for loop. In this snippet I have added simplistic sample data and a mock detectOverlap function that will return true when its two arguments are the same (just for the purpose of the snippet):
// Simplistic mock data and function, just to make the snippet work
var actionArray = [1, 1, 3, 3, 3, 8, 9];
function detectOverlap(a, b) { return a === b; }
// Actual code:
var allGroups = [];
var start = 0;
for (var i = 1; i <= actionArray.length; i++) {
if (i >= actionArray.length || !detectOverlap(actionArray[i-1], actionArray[i])) {
if (i - start > 1) // Remove this condition if you want singletons as well
allGroups.push(actionArray.slice(start, i));
start = i;
}
}
console.log(JSON.stringify(allGroups));
Explanation
The variable start is used as an index in the array, from where the most recently found group should start. That group is not yet actually created, since we do not know where it ends, and so I will call this the "undecided" group. start is initialised at 0 to indicate that the first group will start there.
The loop iterates over the array, but starting at 1. In each iteration it decides whether the "undecided" group (started at start) is complete. The group is considered complete when there is no overlap between the previous and the current element of the array. In that case the previous element is the last element of the "undecided" group. The elements for that group are copied from the array with slice. Note that the second argument of slice is the index of the first element that should not be part of the group. Now that group is stored, and start is put at the current index, where the next (and only) "undecided" group should start.
But as long as the two elements do overlap, no new group should be created (that is why the condition has a !). Instead start remains unchanged, and so this "undecided", "unclosed" group is getting bigger in size.
There is an if just before that slice, which prevents the creation of groups that only contain one element. If you remove that if, then also single elements will be isolated in their own "singleton" groups.
The loop will go up to and including arrayAction.length: this is unusual, since that makes the last i an invalid index. But it is useful, since in that case we still want to finish up the last group that is still "ongoing". So in that case i >= arrayAction.length will be true, and so the detectOverlap function will not be called (because the if condition is already known to be true). The if block will be entered and the final group will be created.

Individual custom start position in Qualtrics through Javascript

I want to use either a slider question or a draggable bar chart in Qualtrics to present to respondents how they answered in former questions. More specifically, I compute a value out of then answers with weighting, and want the slider or bar to be positioned at this value.
Notably, as each respondent has a value (stored in an embedded data field), the position will thereby be individual for each respondent. Piping only works for text fields, as far as I understood the support page.
Based on this question/answer I came to the following code for the bar graph:
Qualtrics.SurveyEngine.addOnload(function()
{
var result = "${q://Field/result}";
var qwidth = $('QID1936~1~track').offsetWidth;
var resrec = ((qwidth*result)/100);
$('QID1936').select('.bar').each(function(name, index) {
name.setStyle({ width: resrec +"px"});
});
});
Basically, I get the result for each respondent out of the embedded data, get the width of the full bar graph, compute the ratio that should be colored based on the result, and update the position of the bar graph (following the mentioned answer).
Funny enough, everything works when done in the console. Also, the embedded data is correctly loaded, qwidth as well.
Two problems arise: it seems resrec could be computed wrongly, as a console.log() spits out 0 instead of the correct value. I assumed this could be somehow as a variable is not recognized as number, but several tries with Number() or 0+var did not change how this works in Qualtrics. In the console, it works just fine.
Also, no update of the bar (or slider with similar code) happens, neither with the correct value nor with the 0 that is produced by Qualtrics.
I search for two things: either a solution to the Javascript problem as described, basically how I can update the bar or slider with embedded data. Or another solution how to directly get embedded data into one of those two question formats as a starting value for each respondent individually.
Thanks for your help or ideas!
Try this:
Qualtrics.SurveyEngine.addOnload(function()
{
var qid = this.questionId;
var result = parseFloat("${e://Field/result}");
var qwidth = $(qid+'~1~track').offsetWidth;
var resrec = ((qwidth*result)/100);
$(qid).select('.bar').each(function(name, index) {
name.style.width = resrec + "px";
});
});
Notes:
It is best not to use a hardcoded QID
In a pipe use e: to refer to an embedded variable. q: is for questions.
Use parseFloat to convert the string to a number
No need to use setStyle if you are only setting one value
One solution proposed by Qualtrics support: when you use bars and/or sliders, piped values are actually possible.
The trick is to have the value of the bar/slider shown (a thing we do not use in the whole survey elsewhere). Then, you can access over the Advanced Question Options > Add Default Choices the blue arrow for piping text behind the value. Through this, the value is individually set to either embedded data or another answer.
Note, however, to tick "Show value" before accessing the default choices, else you will only be able to drag around the bar and set it for all simultaneously.
Here is a solution using the Qualtrics Question API method setChoiceValue that does not require you to compute the ratio and update the length of the bars manually.
Below is an example code for the result of ten respondents saved in embedded data from previous questions.
Qualtrics.SurveyEngine.addOnload(function()
{
var embedded = ["${e://Field/r1}", "${e://Field/r2}",
"${e://Field/r3}", "${e://Field/r4}", "${e://Field/r5}",
"${e://Field/r6}", "${e://Field/r7}", "${e://Field/r8}",
"${e://Field/r9}", "${e://Field/r10}"];
for (var i = 0; i < 11; i++) {
var index = i + 1;
var choiceInput = embedded[i];
this.setChoiceValue(index, choiceInput);
}
});
For one respondent:
Qualtrics.SurveyEngine.addOnload(function()
{
var value = "${e://Field/r1}";
this.setChoiceValue(1, value);
});

Iterate Through Nested JavaScript Objects - Dirty?

I have some JavaScript that I wrote in a pinch, but I think it could be optimized greatly by someone smarter than me. This code runs on relatively small objects, but it runs a fair amount of times, so its worth getting right:
/**
* Determine the maximum quantity we can show (ever) for these size/color combos
*
* #return int=settings.limitedStockThreshold
*/
function getMaxDefaultQuantity() {
var max_default_quantity = 1;
if (inventory && inventory.sizes) {
sizecolor_combo_loop:
for (var key in inventory.sizes) {
if (inventory.sizes[key].combos) {
for (var key2 in inventory.sizes[key].combos) {
var sizecolor_combo = inventory.sizes[key].combos[key2];
if (isBackorderable(sizecolor_combo)) {
//if even one is backorderable, we can break out
max_default_quantity = settings.limitedStockThreshold;
break sizecolor_combo_loop;
} else {
//not backorderable, get largest quantity (sizecolor_combo or max_default_quantity)
var qoh = parseInt(sizecolor_combo.quantityOnHand || 1);
if (qoh > max_default_quantity) {
max_default_quantity = qoh;
};
};
};
};
};
};
return Math.min(max_default_quantity, settings.limitedStockThreshold);
};
First, inventory is a object returned via JSON. It has a property inventory.sizes that contain all of the available sizes for a product. Each size has a property inventory.sizes.combos which maps to all of the available colors for a size. Each combo also has a property quantityOnHand that tells the quantity available for that specific combo. (the JSON structure returned cannot be modified)
What the code does is loop through each size, then each size's combos. It then checks if the size-color combo is backorderable (via another method). If it any combo is backorderable, we can stop because the default quantity is defined elsewhere. If the combo isn't backorderable, the max_default_quantity is the largest quantityOnHand we find (with a maximum of settings.limitedStockThreshold).
I really don't like the nested for loops and my handling of the math and default values feels overly complicated.
Also, this whole function is wrapped in a much larger jQuery object if that helps clean it up.
Have you considered using map-reduce? See a live example of a functional approach.
This particular example uses underscore.js so we can keep it on a elegant level without having to implement the details.
function doStuff(inventory) {
var max = settings.limitedStockThreshold;
if (!(inventory && inventory.sizes)) return;
var quantity = _(inventory.sizes).chain()
.filter(function(value) {
return value.combos;
})
.map(function(value) {
return _(value.combos).chain()
.map(function(value) {
return isBackorderable(value) ? max : value.quantityOnHand;
})
.max().value();
})
.max().value();
return Math.min(quantity, max);
}
As for an explanation:
We take the inventory.sizes set and remove any that don't contain combos. We then map each size to the maximum quantity of it's colour. We do this mapping each combo to either its quantity or the maximum quantity if backordable. We then take a max of that set.
Finally we take a max of set of maxQuantities per size.
We're still effectily doing a double for loop since we take two .max on the set but it doesn't look as dirty.
There are also a couple of if checks that you had in place that are still there.
[Edit]
I'm pretty sure the above code can be optimized a lot more. but it's a different way of looking at it.
Unfortunately, JavaScript doesn't have much in the way of elegant collection processing capabilities if you have to support older browsers, so without the help of additional libraries, a nested loop like the one you've written is the way to go. You could consider having the values precomputed server-side instead, perhaps cached, and including it in the JSON to avoid having to run the same computations again and again.

Categories

Resources