Array logic match for list - javascript

I have a quick links widget with different types of links/menus that the user can choose from. Only four different menu options can be shown at the same time - not more or less.
In code I first extract all the menu options which come in the form in [1,2,3...] which corresponds to the rows in a list where the menu options is stored.
The user chooses menu options is also returned in the same way with an array like [2,3,8,9] with the number corresponding which row to get from the list.
Example:
All menu/widgets
Travel
Hotel
Car
Buss
Airplane
Holiday
This will return an array [1,2,3,4,5,6]
And if I choose to save hotel, buss, airplane and holiday then my user settings will return [2,4,5,6].
Problem:
It works, until a widget is deleted from the list that the user has saved then the widget only will show three menus/links. I want the widget to always show four links, so if one is missing I need to populate the array. So if its missing, I want to show another link. It would be good, but not needed, to take a link that is set to default when its missing (always the first four in the list). I have set up a logic for that but its not working.
Code:
public async getUserWidgets(): Promise<Widget[]> {
return new Promise<Widget[]>(async(resolve, error) => {
let allWidgets = await this.getAllWidgets(); // Returns an array of all links [1,2,4...]
let userRepository = new UserProfileRepository(this.absoluteWebUrl);
let userSettings = await userRepository.getUserExtensionValues(this.context); //contains the user saved widgets ex [2,3,6,7]
var result:Widget[] = [];
// if the user has no settings, or less than 4 saved links
if (userSettings == null || userSettings.QuickLinksWidgets == null || userSettings.QuickLinksWidgets.length < 4) {
result = allWidgets.filter((w) => {return w.defaultWidget;}).slice(0,4); //default widget but not really needed.
}
else {
var ids = userSettings.QuickLinksWidgets;
for (let i = 0; i < 4; i++) {
let id = '' + ids[i];
let w = allWidgets.filter((e) => { return e.id == id;});
if (w.length == 0) {
continue;
}
result.push(w[0]);
}
};
resolve(result);
}); }

From what you described, it sounds like maybe you're not updating properly (calling getUserWidgets when userSettings.QuickLinksWidgets changes? First check to make sure it's called as you expect.
If getUserWidgets is being called properly, try to add defaults to their settings until you have 4 links total. Right now you are using default links if they have any less than 4 in their settings.
For example:
// take up to 4 user links and make sure we don't exceed the length of the array
for (let i = 0; i < 4 && i < userSettings.QuickLinksWidgets.length - 1; i++) {
// get the id of the widget in the user's settings
let widgetId = userSettings.QuickLinksWidgets[i].id
// find the widget with a matching id and add it to our results
result.push(allWidgets.find(w => w.id === widgetId)
}
// if there's not already 4 links, add more to our list
let j = 0
while (result.length < 4) {
// check the first 4 user links to make sure we didn't include this link already
if (!userSettings.QuickLinksWidgets.slice(0, 4).includes(allWidgets[j].id)) {
// add the new widget to the results
result.push(allWidgets[j])
}
j++
}
resolve(result)

Related

Search with document.querySelectorAll based on class, text content

I have a search box helping users navigate the rooms at school via this link. Trying to search for user-based input, whereas if there is anything user entered that matches the query Selector based on class + text content within an SVG rect element (room number text or school class name) is taking a user to a certain floor + highlights the room. The part I'm struggling with is as follows in this floors.js file:
function searchCabinet() {
let userInput = document.getElementById("search").value;
let isCabinetFound = false;
if (document.querySelectorAll('.rooms').textContent.includes(userInput)) {
isCabinetFound = true;
selectFloor('.rooms', userInput);
}
I have assigned the class .rooms to divs found in SVG floor files.
Floor0 as an example. Also, have floor1.svg, floor2.svg and floor3.svg
Should the above be put into for loop?
The previous version had getElementbyID, but that would search for the exact room name specified in Map array. So if I entered room 157. with a dot after a number, that would return 0 results. Since the room planner is going to have class names too, like 5.a room 157. Searching by .textContent or .innerText would make more sense.
The final idea is to get it to run like this example. The drawback with this, again, is that if the user enters 157. with a dot it fails to search. Since it only searches by unique ID. While what's needed is searching by any text that is within the container box (room number, class number, might be even room volume in the future).
I have two ideas. But first add the room names and the numbers as classes like class="name-lab num-1". With that no need to filter elements.
const floorCount = 4
var selectedFloor = 0
function nextOrSelectFloor(select = null) {
if (select != null) {
//Iterate by one but if it's last element set to zero
if (selectedFloor == floorCount - 1) selectedFloor = 0
else selectedFloor += 1
} else selectedFloor = select
document.getElementById("floor-map").setAttribute("data", `img/floor${selectedFloor}.svg`)
}
var selectedCabIndex = 0
let matchedRooms = []
function searchCabinet() {
//Clean the list before new search
selectedCabIndex = 0
matchedRooms = []
let userInput = document.getElementById("search").value;
//We will search each floor once
const floorSearchLimit = floors.length
for (let i = 0; i < floorSearchLimit; i++) {
document.querySelectorAll(
`[class*='name-${userInput}'],
[class*='num-${userInput}']`
).forEach(e => matchedRooms.push({
cab: e, //Push the cab and the floor number to select
floor: i
}))
nextOrSelectFloor()
}
if (matchedRooms.length > 0) selectNextCab()
else {
//Cab not found
document.getElementById('alert').classList.remove("hide");
document.getElementById('alert').classList.add("show");
document.getElementById('alert').classList.add("showAlert");
setTimeout(function () {
closeAlert();
}, 5000);
}
}
//Add button to page and set onclick
function selectNextCab() {
const previousSlectedCab = matchedRooms[selectedCabIndex - 1]
previousSlectedCab.cab.classList.remove("highlight")
//This function runs after search and should start from zero, so first highlight then increase index
const selectedCab = matchedRooms[selectedCabIndex]
nextOrSelectFloor(selectedCab.floor)
selectedCab.cab.classList.add("highlight")
selectedCabIndex += 1
}
You can define the next page with a variable. If don't go to next page add all SVG files to page and give display:none to them with a class. This would be more suitable. That's your decision

How do I prevent duplicate API Calls in JavaScript FOR Loop

I have an array of URLs I scraped from a webpage and then make an API call to validate the URLs to see if they are malicious. The only problem is I am limited to a certain amount of API calls per day and the array contains duplicate URLs. I am trying to loop through the array and used a saved API call for duplicate values and I am struggling to find the best way to do it since there could be multiple duplicates. If the loop encounters a duplicate value I want it to not make the API call and just return the already saved values from the previous API call. I included some basic sudo code inside the code below and I am unsure of what to populate the sudo code with.
/* urlToValidate is a list of URLS */
urlToValidate = ["ups.com", "redfin.com", "ups.com", "redfin.com", "redfin.com", "redfin.com"];
var isValid = false;
/* API Overview https://www.ipqualityscore.com/documentation/malicious-url-scanner-api/overview */
for (let i = 0; i < urlToValidate.length; i++) {
if (i == 0 || Is Not A DUPLICATE) {
$.getJSON('https://ipqualityscore.com/api/json/url/<API_KEY>/' + urlToValidate[i], function( json ) {
if (!json.phishing && !json.spamming && json.risk_score < 80) {
isValid = true;
returnMessage(isValid, json.risk_score, i)
} else {
isValid = false;
returnMessage(isValid, json.risk_score, i)
}
});
} else {
returnMessage(alreadySaved duplicateValue, alreadySaved duplicate risk_score, i)
}
}
Desired Output:
URL Valid: true Risk Rating: 0 Position: 7
or
Duplicate URL: true Risk Rating: 0 Position: 7
This is a simple matter of caching.
Outside of your for loop, maintain some kind of mapping of URLs to their corresponding fetch results. That way, you can store not only whether that URL has been called but also the result, if it exists. An easy way to do that is with a basic object, where the "keys" are strings corresponding to the URLs, and the "values" are the results of your fetches.
const resultCache = {};
Inside of your loop, before you do a fetch you should first check whether the cache already has a result for that URL.
let result;
if (resultCache[urlToFetch]) {
result = resultCache[urlToFetch];
} else {
// use the previous result
result = await fetch(/* whatever */);
// remember to also store result in cache
resultCache[urlToFetch] = result;
}
You have a few options.
First you could convert your urls to a Set which prevents any duplicates from occurring at all.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
Another option would be to store the return in an object with the key being the url and in your if statement check to see if the value is not null.
*** UPDATE using a set ***
/* urlToValidate is a list of URLS */
urlToValidate = ["ups.com", "redfin.com", "ups.com", "redfin.com", "redfin.com", "redfin.com"];
var urls = new Set(urlToValidate);
var isValid = false;
/* API Overview https://www.ipqualityscore.com/documentation/malicious-url-scanner-api/overview */
for (let i = 0; i < urls.length; i++) {
$.getJSON('https://ipqualityscore.com/api/json/url/<API_KEY>/' + urls[i], function( json ) {
if (!json.phishing && !json.spamming && json.risk_score < 80) {
isValid = true;
returnMessage(isValid, json.risk_score, i)
} else {
isValid = false;
returnMessage(isValid, json.risk_score, i)
}
});
}
}

JS Web Scraper Hangs on Evaluation

I'm building a scraper that needs to get data from multiple adjacent elements. Essentially there are headers (h3) that display categories, next to which there are tables of ranks. I'm trying to crawl the page for a String to find out if that String was ranked in a category and, if so, what rank it achieved (A, B, or C), then fill an array with objects that describe what the categories and ranks that string achieved (phew).
Initially, I generated an error in the while loop ("cannot define property 'tagName' of null"), as sib kept evaluating to null for some reason. I added a test, in case it was happening at the end of arr, but now the code just hangs indefinitely. I have a feeling that sib isn't being defined immediately, but I can't put my finger on if so or why.
I am testing everything in Chrome DevTools, if that helps anything. Also I am not using a Fat Arrow function in DevTools to test this out, so it is not contributing to this problem per se, but if it will screw me over in the long run please let me know!
(str) => {
let h = document.getElementsByTagName('h3');
let arr = [];
let res = [];
for ( let i = 0; i < h.length; i++){ //Filter out any h3's that are not category headers.
h[i].id.includes('category-') && arr.push(h[i]);
};
for ( let i = 0; i < arr.length; i++){
let head = arr[i].innerText; //Save the current category header.
let sib = arr[i].nextElementSibling; //Should be a table containing rank A.
while ((sib != null) && (sib.tagName == 'TABLE')){
if(sib.innerText.includes(str)){ //Create an object if the rankings table contains our target.
let hit = {};
hit.category = head;
hit.rank = sib.children[0].innerText;
res.push(hit);
}
else{ //Go to the next table which contains the next rank.
sib = sib.nextElementSibling;
};
};
};
return res;
}

Optimization of a large multiselect list

I have three MultiselectLists
Countries which has 22 values
Cities which has ~800 values
Sites which has ~1700 values
I am using the jquery bootstrap-multiselect library
It is fast enough for what I am trying to do that the user doesn't notice any overhead except in one case when the user selects USA 600 Cities and 1200-1300 sites get checked/selected this takes about 30 seconds in IE, and about 6 seconds in Firefox which is enough for a reasonable person to believe that something is wrong with the page.
I don't want to change the UI of the website at all since this works beautifully in every other case, including when the user hits a "select-all" or "deselect-all" and just wants to run a full report
these are the two functions that take about 90-95% of the time to execute
getInputByValue: function(value) {
var checkboxes = $('li input', this.$ul);
var valueToCompare = value.toString();
for (var i = 0; i < checkboxes.length; i = i + 1) {
var checkbox = checkboxes[i];
if (checkbox.value === valueToCompare) {
return $(checkbox);
}
}
}
getOptionByValue: function(value) {
var options = $('option', this.$select);
var valueToCompare = value.toString();
for (var i = 0; i < options.length; i = i + 1) {
var option = options[i];
if (option.value === valueToCompare) {
return $(option);
}
}
}
and this is the code I use to select all of the options, I have a Dictionary>>(); that I pass from my controller into my view that governs the relationship between the Countries/Cities/ and sites
onChange: function (option, checked, select) {
try{
if (checked == true) {
var t0 = performance.now();
$('#Cities').multiselect('select', Object.keys(CountryCitySite[$(option).text()]),'triggeronChange');
var t1 = performance.now();
list = Object.keys(CountryCitySite[$(option).text()])
var t2 = performance.now();
for(var i = 0; i<list.length; i++)
{
$('#Sites').multiselect('select', CitySite[list[i]])
}
var t3 = performance.now();
}
else if (checked == false) {
$('#Cities').multiselect('deselect', Object.keys(CountryCitySite[$(option).text()]), 'triggeronChange');
list = Object.keys(CountryCitySite[$(option).text()])
for(var i = 0; i<list.length; i++)
{
$('#Sites').multiselect('deselect', CitySite[list[i]], 'triggeronChange')
}
}
}
Some thoughts:
1. maybe in the case of USA I could do an Ajax, and post everything to the server and then return three new selectlists with the appropriate options checked? the problem with this is that I don't see this taking less than like 7 or 8 seconds if not more which is still much too long
var options = $('option', this.$select);
this Jquery selection is several orders of magnitude slower than just using native javascript Document.getelementsbytagname, since I know that all of the checkboxes have unique values maybe I could replace this Jquery selection with native javascript and get all of the checkboxes that way
as a 'hack' I could send two invisible multiselectlists at the very beginning with all of the boxes for USA checked, these invisible multiselects behave exactly the same as the visible ones in most respects but if the user selects USA, these are shown instead of the originals. This actually does work, it makes the website a little slower but since the selection of every other option is so quick it doesn't really matter, it seems like a sub-par solution but this is currently the best I have
if anyone thinks of anything else or can give any advice on this I would be very appreciative
Sincerely Josh

deleting an item from array and add it to another array

I'm trying to delete an item from an array and add it to another array. The array states consists of a list of 50 states. User needs to enter the name of a state and the state must get deleted from states array and get added to the array correctState. Here is my code
function searchString()
{
var total = 50;
var string = document.getElementById("txtState").value;
var element = document.getElementById("status");
for (i=0; i < states.length; i++)
{
if(states[i].toUpperCase() == string.toUpperCase())
{
count = count + 1;
//element.innerHTML = "CORRECT!!!"
addElem = states.splice(i,1);
correctState.push(addElem);
/*for (j=0; j < correctState.length; j++)
{
if(correctState[j].toUpperCase() == string.toUpperCase())
{
element.innerHTML = "Already Submitted. Try another State";
}
}*/
}
document.getElementById("score").innerHTML = count +"/"+ total;
document.write(states);
document.write(correctState);
}
}
Enter State : <input type="text" name="txtState" id="txtState"><span id="timer"></span><br /><span id="status"></span>
<button type="button" name="btnPlay" id="btnPlay" accesskey="s" onClick="searchString()"><u>S</u>ubmit</button>
I'm not able to achieve what I need. I'm new to javascript and need help.
Re these lines:
addElem = states.splice(i,1);
correctState.push(addElem);
splice doesn't return the element that you remove, it returns an array of those elements. So your code is pushing array instances onto correctState. I'd do this:
correctState.push(states[i]); // First push the matching state onto `correctStates`
states.splice(i,1); // ...then remove it
Alternately, you could do it in the order you showed, you just have to get the removed element out of the array you get back
addElem = states.splice(i,1);
correctState.push(addElem[0]);
// Here -----------------^^^
but again, I'd do it by pushing first, then removing.
Separately, I'd use the debugger built into your browser to single-step through that code and watch it run. I suspect you'll find that you want to move some things around, and you almost certainly want to stop looping once you've found that string matches something in the array.
My guess is that it's the fact that you're modifying your states array while you are still enumerating.
So say you're states array is [AR, LA, CT] and the user entered LA. So your for loop goes like this
i = 0 (length is 3 so i < 3)
string not found
i = 1 (length is 3 so i < 3)
string found, remove it from states
i = 2 (length is 2 so i < 2 fails and there's no final loop)
What you probably want is just something like this
function moveAllInstancesBetweenArrays(val, source, destination) {
var indexOfVal = source.map(function(s) { return s.toUpperCase() })
.indexOf(val.toUpperCase());
if(indexOfVal == -1)
return;
source.splice(indexOfVal, 1);
destination.push(val);
moveAllInstancesBetweenArrays(val, source, destination); //continue until all matching are moved
}
moveAllInstancesBetweenArrays(yourSearchVal, states, correctStates);

Categories

Resources