How to get value and change outer variable inside event handlers? - javascript

I have empty array. Need to fill it by clicking some links (only if value of current index is not filled already).
$(document).ready(function () {
var genres_items = [];
$('.genre-fill-link').on('click', function() {
var genre_index = $(this).data('g-index'); // get id of genre
if(!genres_items[genre_index] { // want to check is this genre filled - get error, undefined variable
$.get('/get-genre-list/', {'genre-id', gener_index}, function(data) { // if genre is not filled yet, want to get data by ajax
gener_items[gener_index] = data;
});
}
}
console.log(genres_items); // get empty untouched array, even if links clicked
});
How to fill all elements of array genre_items (every element once by clicking .genre-link) ?
How to get values of this array in others handlers and callbacks afterwards?!
Javascript confused me =\ Please help

check my comments and try this :
$(document).ready(function () {
var genres_items = [];
$('.genre-fill-link').on('click', function() {
var genre_index = $(this).data('g-index'); // get id of genre
if(!genres_items[genre_index] { // want to check is this genre filled - get error, undefined variable
$.get('/get-genre-list/', {'genre-id', gener_index}, function(data){ // if genre is not filled yet, want to get data by ajax
genres_items[genre_index] = data;
console.log(genres_items);
});
}
}
});

Related

Optimize Javascript code to find records in array that match criteria

I have a parent component which is a list of records and in one of the child component I have a form that submits data and if successful is added to that list in the parent. Everytime the data is submitted, I need to check if there is an identical record with the same title. This child form component is used to add and edit records, so if the record is edited then I also have to check that it can be submitted with the same name of ofcourse. Below is the code I have and it is working fine but I have been thinking if there is a better way of writing this. Can it be done while going thru the list array the first time instead of going through it once and then going thru it again to check for unique items.
When the data is submitted in the child component (form) I am executing the following functions to see if title field is unique.
const isUniqueTitle = (title) => {
if(activities.find(activity => activity.title.toLowerCase() === title)){
// shows alert
return false;
}
return true;
}
// Child component/form calls this function with the form data
const validateData = data = {
let isUnique = true;
//activities below is available here in the parent
activities.forEach(activity => {
// check for id below tells me that its a record being edited so only do a check if the title
// has been changed else if there is no id then it means its a new record so continue with the
// check
if (activity.id && activity.title != activity.title) {
isUnique = isUniqueTitle(data.title);
} else if (!activity.id) {
isUnique = isUniqueTitle(data.title);
}
return isUnique;
})
}
Please advise, thank you!
You could use a Set to store titles and use its has method to check for the uniqueness of any given title
The Set object lets you store unique values of any type
const titleSet = new Set(activities.map(activity => activity.title.toLowerCase()))
const isUniqueTitle = (title) => {
return titleSet.has(title);
}
// Child component/form calls this function with the form data
const validateData = data = {
//activities below is available here in the parent
activities.forEach(activity => {
// check for id below tells me that its a record being edited so only do a check if the title
// has been changed else if there is no id then it means its a new record so continue with the
// check
if (activity.id && activity.title != activity.title) {
isUniqueTitle(data.title);
} else if (!activity.id) {
isUniqueTitle(data.title);
}
})
}

Getting undefined variable when calling function from another function

Scratching my head. I'm getting an undefined error when trying to call a function. Thing is, when I print to the console I can see the data being passed clearly.
Uncaught TypeError: convos is undefined
function1
function fetchConversation(userID){
//loop through
usersms.forEach(function (sms,counter) {
//get userid from iteration
var userid_loop = sms.details[0].user.id;
//only display convo from specific user
if(userid_loop === userID){
//get all messages from this one user
var conversation = sms.details;
//transfer conversation to next function to display
showConversation(conversation);
//
}
//
});
}
function2
function showConversation(myconvo){
var convos = myconvo;
//iterate and append conversion
convos.forEach(function (msg,counter) {
console.log(msg.message);//prints all messages in the log
});
}
showConversation()//Uncaught TypeError: convos is undefined
I believe that you need to enter something within the brackets of showConversation().
You are assigning convos to myconvo, but myconvo doesn't exist because you didn't enter it as a parameter (value in the brackets).
Your first error is that you are not passing in any arguments to the function.
Your second error is that msg.message doesn't exist - it's just msg by itself.
Also, the counter is unnecessary in this case.
function showConversation(myconvo) {
var convos = myconvo;
//iterate and append conversion
convos.forEach(function(msg) {
console.log(msg); //prints all messages in the log
});
}
showConversation(["element1", "element2"])
You also have an error here:
function fetchConversation(userID){
//loop through
usersms.forEach(function (sms,counter) {
//get userid from iteration
var userid_loop = sms.details[0].user.id;
//only display convo from specific user
if(userid_loop === userID){
//get all messages from this one user
var conversation = sms.details; //actually gets the details of the element it is iterating through, not all of them
//transfer conversation to next function to display
showConversation(conversation); //sends the details one by one for each iteration
//
}
//
});
}
Fix:
function fetchConversation(userID){
var conversation=[]
//loop through
usersms.forEach(function (sms,counter) {
//get userid from iteration
var userid_loop = sms.details[0].user.id;
//only display convo from specific user
if(userid_loop === userID){
//get all messages from this one user
conversation.push(sms.details);
}
});
//transfer conversation to next function to display
showConversation(conversation);
}

Each click triggers different API call

I have list and each clicked item triggers different API request. Each request have different duration. On success I'm displaying some data.
Issue is that when I click on item#1 which takes approx 6000 to load, and just after on item#2 which takes 2000 to load, I will have the last clicked item displayed - which is item#2 because it has already loaded and once item#1 has received data my data will change to that. This is wrong as I want to display data from the latest click.
This is how I handle event:
newList.on('click', 'li', (e) => {
let id = $(e.currentTarget).data("id");
store.getCharacterDetails(id).then(docs => {
this.clearDetails();
this.charDetails = docs;
this.displayDetails(this.charDetails);
})
My API is a simulation from store object.
I suppose this works as expected but I do want the last triggered request to be valid.
A crude and simple method can be creating an array and pushing the IDs and after the asynchronous operations you can just check if it is the latest click or not. But pitfall is that if clear and displayDetails takes much time and if someone click while it was clearing and displaying it will not register the latest click.
Anyway, here is the code maybe you can make something better out of it.
var latestClick = [];
newList.on('click', 'li', (e) => {
let id = $(e.currentTarget).data("id");
latestClick.push(id);
store.getCharacterDetails(id).then(docs => {
if(id === latestClick[latestClick.length - 1]){
this.clearDetails();
this.charDetails = docs;
this.displayDetails(this.charDetails);
latestClick = [];
}
})
})
Make charDetails an object that keeps all of the results, keyed by the ids. Keep track of the last clicked id.
// in constructor
this.charDetails = {};
this.lastId = null;
newList.on('click', 'li', (e) => {
let id = $(e.currentTarget).data("id");
this.lastId = id;
if (this.charDetails[id] === id) { // don't cancel requests, cache them!
this.displayDetails(this.charDetails[id])
} else {
store.getCharacterDetails(id).then(docs => {
// this runs later, cache the result
this.charDetails[id] = docs;
if (id === lastId) { // only update UI if the id was last clicked
this.displayDetails(docs)
}
});
}
});

How do I submit an array from the client to a server (ExpressJs) using AJAX?

I'm working on a web application in which a user can drag and drop div elements, whose content is generated from a database, into their preferred order. I want the user to be able to, when they are finished arranging the divs, submit their list (in order) to the server and store the new order in a table. I'm using AJAX to do this, however I'm not sure if that is necessary/the proper way to do this, since I don't need to asynchronously update the page (I just need to submit the data). I've tried a variety of methods to get my array to the server in a usable format (it needs to be iterable and allow for element locations to be compared). I have tried using JSON.stringify/parse, creating a custom object, simply submitting the array on its own, and so on. Here is my code, the most relevant bits are towards the bottom by the makeRouteArray function. Ideally to accomplish this I would like to use just JavaScript (no jQuery). Finally, please excuse my messy code, I'm learning.
// get two groups of elements, those that are draggable and those that are drop targets
let draggable = document.querySelectorAll('[draggable]');
let targets = document.querySelectorAll('[data-drop-target]');
// div immediately surrounding bus routes
var busList = document.getElementById("bus-list");
const button = document.getElementById("button");
// store the id of the draggable element when drag starts
function handleDragStart(e) {
e.dataTransfer.setData("text", this.id); // sets 'text' value to equal the id of this
this.classList.add("drag-start"); // class for styling the element being dragged, sets opacity
}
function handleDragEnd(e) {
e.target.classList.remove('drag-start');
}
function handleDragEnterLeave(e) {
// should provide visual feedback to user?
}
// handles dragover event (moving your source div over the target div element)
// If drop event occurs, the function retrieves the draggable element’s id from the DataTransfer object
function handleOverDrop(e) {
e.preventDefault();
var draggedId = e.dataTransfer.getData("text"); // retrieves drag data (DOMString) for specified type
var draggedEl = document.getElementById(draggedId);
draggedEl.parentNode.insertBefore(draggedEl, this); // inserts element being dragged into list
var draggedArray = Array.from(draggedEl.parentNode.children); // creates new array which updates current location of each route
e.target.classList.remove('drag-start'); // sets opacity back to 1
// if (e.type === "drop") {
// // when dropped, update localstorage
savePage(draggedArray);
// }
}
// get each full bus-route div in #bus-list with p content as single arr item each
// called when item is dropped
function savePage(dArray) {
// honestly i can't remember what this does precisely
// but i can't seem to add to localstorage in the way i want without it
var arr = Array.prototype.map.call(dArray, function(elem) {
return elem.outerHTML;
});
localStorage.newList = JSON.stringify(arr); // have to stringify the array in order to add it to localstorage
}
// ideally it should just update the order of the bus routes to reflect local storage
// and add classes/ids to the divs etc. (hence using outerHTML)
function makePage() {
// getting the item from localstorage
var getData = localStorage.getItem("newList");
// parsing it back into an array
var parsedData = JSON.parse(getData);
// string to hold contents of array so they can be display via innerHTML
var fullList = "";
if (localStorage.getItem("newList") === null) {
return; // maybe this is wrong but if there's nothing in local storage, don't do anything
} else {
for (let i = 0; i < parsedData.length; i++) {
fullList = fullList + parsedData[i];
}
busList.innerHTML = fullList;
// reassigning targets after calling function in order to re-register event handlers
draggable = document.querySelectorAll('[draggable]');
targets = document.querySelectorAll('[data-drop-target]');
}
}
// probably better way to do this
for (let i = 0; i < draggable.length; i++) {
draggable[i].addEventListener("dragstart", handleDragStart);
draggable[i].addEventListener("dragend", handleDragEnd);
}
// drop target elements
for (let i = 0; i < targets.length; i++) {
targets[i].addEventListener("dragover", handleOverDrop);
targets[i].addEventListener("drop", handleOverDrop);
targets[i].addEventListener("dragenter", handleDragEnterLeave);
targets[i].addEventListener("dragleave", handleDragEnterLeave);
}
// rolling average: new_average_score = old_average_score * (total_users-1)/total_users + user_route_rank/total_users
// user id, column per route (score)
// session id
// submit button to save changes to db
// when submit is clicked
// get current results from either local storage or from currently ordered + displayed list
// data will be in array format
// do i need AJAX to be able to submit something other than form data to a server?
// is submitting data in this format (taken from page's HTML ordering) even possible?
// i'd prefer to not use jQuery
// how do i submit an array to the server (express) and then parse (?) it to make it useable?
// so far i have tried JSON.stringify/parse, making a custom object, just submitting array, etc.
// ultimately what i need is a data type that i can loop over and compare positions of stored elements
var makeRouteArray = function() {
var currentOrderArr = Array.from(busList.children);
var idData = currentOrderArr.map(function(el) {
return Number(el.id.slice(2));
});
return idData; // not sure if having two return statements like this is okay
};
button.addEventListener("click", function(e) {
var request = new XMLHttpRequest();
request.open('POST', '/submit', true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.send(data);
});
makePage();
The makeRouteArray function is taking ids from an HTML ul and stripping them of the first two characters because I only want to use the numbers (the ids are structured as #r-num). An array of these numbers is what I would like to submit to the server and if possible, once on the server, return the JSON object to an array.
Thanks!

Passing JavaScript select value between forms

I'm in need of some expert JavaScript advice. I'm coding using Electron.
The issue: I'm trying to capture the value selected from the second of two dropdown lists and pass it back into a JavaScript file. The code below is ordered as it would run. The dropdown code is not shown as it is simply populated by the viewProvinces function below. The first dropdown's id is "country-select" while the second is "province-select".
In this case, a link is clicked in Index.html which calls the anonymous function in Data.js. The anonymous function calls viewProvinces that populates the parray/data variables from the anonymous function which produces the alert showing the value returned.
(FYI) viewProvinces also populates the second dropdown (id province-select) by filtering based on the values produced in the first dropdown (id country-select). For example, if Afghanistan is selected as a country in the first, then only provinces from Afghanistan are shown in the second.
Moving on, viewProvinces calls Provinces which is an array populated when it calls getProvinces after querying a SQLite database for the source data.
ViewProvinces, Provinces, and getProvinces all work correctly. The link and the anonymous function are the issue and technically work in that they produce a result, but not the correct result. When the link is clicked it produces "object Object". I believe I understand why it is doing this, though I am not skilled enough (yet) to know how to fix it. I do not know how to adjust the code so that it returns the actual value(s) selected from the second (provinces) dropdown.
Put simply, the data is gathered from a SQL query that populates a series of arrays that further populates the dropdown list. The value of this list, once selected, should be returned back to the source JavaScript file into a variable (it fails here).
Apologies if this sounds convoluted, but I'm trying to be thorough. Any help in this matter would be greatly appreciated! Thank you!!
Index.html:
<a id="test-select" href="#">test</a>
Data.js:
$( "#test-select" ).click(function(e) {
e.preventDefault();
var parray = viewProvinces($("#country-select").val());
var data = $('#test-select').data(parray);
alert(data);
});
View.js:
function viewProvinces(ccode) {
var viewPro = Provinces(function(results) {
// Code only gets triggered when Provinces() calls return done(...);
var container = document.getElementById('province-select');
var fragment = document.createDocumentFragment();
results.filter(function(el) {
return el.ccode === ccode;
}).forEach(function(loc, index) {
var opt = document.createElement('option');
opt.textContent = loc.pname;
opt.value = loc.pcode;
fragment.appendChild(opt);
});
container.appendChild(fragment);
});
}
Model.js:
function Provinces(done) {
//Pull location values from data
return getProvinces(done);
}
Data.js:
function getProvinces(done) {
var sqlite3 = require('sqlite3').verbose();
var file = 'db/locations.sqlite3';
var db = new sqlite3.Database(file);
var stmt = 'SELECT Country.CountryId, Country.CountryCode, Country.CountryName, Province.ProvinceName, Province.ProvinceCode FROM Province INNER JOIN Country ON Province.CountryId = Country.CountryId'
var larray = [];
db.all(stmt, function(err, rows) {
// This code only gets called when the database returns with a response.
rows.forEach(function(row) {
larray.push({
ccode: row.CountryCode,
cname: row.CountryName,
pname: row.ProvinceName,
pcode: row.ProvinceCode
});
})
return done(larray);
});
db.close();
}
I have tried to answer your question via a fiddle based on your code created here but I had some trouble understanding a couple of things in your code, so I might have missed something. The main change I made was to make the Provinces(function(results) {.. function return the array of filtered provinces:
function viewProvinces(ccode) {
return Provinces(function(results) {
var provinces = [];
// Code only gets triggered when Provinces() calls return done(...);
var container = document.getElementById('province-select');
var fragment = document.createDocumentFragment();
results.filter(function(el) {
return el.ccode === ccode;
}).forEach(function(loc, index) {
var opt = document.createElement('option');
opt.textContent = loc.pname;
opt.value = loc.pcode;
fragment.appendChild(opt);
provinces.push(loc);
});
container.appendChild(fragment);
return provinces;
});
Once this is done, the parray is correctly setup:
var parray = viewProvinces($("#country-select").val());
However, I was confused when I read this code:
var data = $('#test-select').data(parray);
alert(data);
I assumed you were trying to save the provinces data in the link's store, so modified the code as follows to demo that it works:
$('#test-select').data({
provinces: parray
}); // Save the provinces array
var data = $('#test-select').data(); // Retrieve the provinces array
//Dump it on the console
$.each(data.provinces, function(index, province) {
console.log("Province[" + index + "].name: " + province.cname);
});

Categories