Script is working, but only with a "hack" - javascript
Background:
I am part of a large family and to save everyone some money at Christmas, we do a Secret Santa of sorts for gift giving. I am writing this script so that this all can be managed via a spreadsheet since our process can be somewhat messy. The rules are:
Each "Santa" is given two names that they must buy gifts for.
Those 2 names can not be the same.
Couples can not give gifts to each other or their children. Children
can not give gifts to their siblings or their parents.
Here is a table with some example data:
The Problem
I believe my issue is occurring because of the following code:
//Remove disallowedNames from currentAvailableNames
for (j=0; j<disallowed.length; j++){
var disallowedName = disallowed[j];
currentAvailableNames.splice(currentAvailableNames.indexOf(disallowed[j]), 1);
}
For some reason, the disallowed name(s) are also being removed from the availableNames array and I have no idea why. The only way I have been able to "fix" it, is by adding in the following code after the recipient has been picked:
//Add Disallowed Names back to Available Names Array
for (k=0; k<disallowed.length; k++){
var disallowedName = disallowed[k];
if (disallowedName.length >0) {
availableNames.push(disallowedName);
}
}
Original Code
function giftAssignments() {
//Get Settings
var ss = SpreadsheetApp.getActiveSpreadsheet();
var settings = ss.getSheetByName("Settings");
var resultsSheet = ss.getSheetByName("Results");
var numOfAssignments = settings.getRange("B2").getValue();
var minPrice = settings.getRange("B3").getValue();
var maxPrice = settings.getRange("B4").getValue();
var firstName = settings.getRange("B5").getValue();
var santasLastRow = settings.getLastRow();
var santasLastCol = settings.getLastColumn();
var santasTotal = santasLastRow - firstName + 1;
var santasAsRange = settings.getRange(firstName,1,(santasLastRow - firstName + 1), santasLastCol).getValues();
//Create Santas Array (santas)
var santas = []
for (var i=0; i<santasAsRange.length; i++) {
var name = santasAsRange[i][0];
var email = santasAsRange[i][1];
var disallowedAsString = santasAsRange[i][2];
disallowedAsString = disallowedAsString.replace(", ",",");
var disallowed = disallowedAsString.split(",");
disallowed.push(name);
var santa = [];
santa[0] = name;
santa[1] = email;
santa[2] = disallowed;
santas.push(santa);
}
//Create Array of Names (availableNames)
var availableNames = [];
for (i=0; i<santas.length; i++) {
var aName = santas[i][0];
availableNames.push(aName);
}
//Assign Recipients
var results = assignRecip(santas, availableNames);
Logger.log("RESULTS = " + results);
}
function assignRecip(santas, names) {
var availableNames = names;
for (i=0; i<santas.length; i++) {
var currentAvailableNames = availableNames;
var name = santas[i][0];
var disallowed = santas[i][2];
Logger.log("Santa = " + name);
Logger.log("availableNames = " + availableNames);
//Remove disallowedNames from currentAvailableNames
for (j=0; j<disallowed.length; j++){
var disallowedName = disallowed[j];
currentAvailableNames.splice(currentAvailableNames.indexOf(disallowed[j]), 1);
}
Logger.log("currentAvailableNames = " + currentAvailableNames);
//Pick Random Ricipient from currentAvailableNames
var recipient = currentAvailableNames[Math.floor(Math.random() * currentAvailableNames.length)];
Logger.log("Recipient = " + recipient);
//Add Recipient to Santa Array
santas[i].push(recipient);
//Add Disallowed Names back to Available Names Array
for (k=0; k<disallowed.length; k++){
var disallowedName = disallowed[k];
if (disallowedName.length >0) {
availableNames.push(disallowedName);
}
}
//Add Recipient to Disallowed Names Array
santas[i][2].push(recipient);
//Remove Recipient from Available Names Array
availableNames.splice(availableNames.indexOf(recipient),1);
Logger.log("availableNames = " + availableNames);
Logger.log(" ");
}
return santas;
}
They're references to the same Array. This code doesn't copy the Array itself. It copies the reference to the Array.
var currentAvailableNames = availableNames;
You can fix it using .slice().
var currentAvailableNames = availableNames.slice();
Now you have two separate Arrays, so direct modifications to currentAvailableNames will not affect availableNames.
Note that this is a shallow clone. If it was an Array of Objects or Arrays, then modifications to the nested Object would still be visible from both Arrays.
Related
Getting an error with the click function in Javascript
I am building an application that takes in grade and then gives the average. It also has a sort button that makes it so you can sort by the last name of the student entered and a clear button to clear the current values that are in the display array. Here is the code I have so far for the javascript file: var $ = function (id) {return document.getElementById(id);} "use strict"; var scoreArray = []; var dispArray = []; var displayScores = function () { var totalScore = 0; var numberOfScores = 0; var averageScore = 0; numberOfScores = scoreArray.length; //loop to find the total score for (var i=0;i<numberOfScores;i++) { totalScore = totalScore + scoreArray[i]; } //find the average averageScore = totalScore/numberOfScores; var st=""; //put the string in the display array for(var i=0; i<numberOfScores;i++) { st += (dispArray[i]+"\n"); } //display the average score $("#average_score").val(averageScore.toString()); $("#scores").val(st); }; $("#add_button").click(function(){ var scoreNumber = parseInt( $("#score").val()); var scoreString = $("#last_name").val() + ", " + $("first_name").val() + ": " + $("#score").val(); scoreArray.push(scoreNumber); dispArray.push(scoreString); displayScores(); //reset the values $("#first_name").val(""); $("#last_name").val(""); $("#score").val(""); $("#first_name").focus(); }); //function to clear the contents of the form $("#clear_button").click(function(){ //empty the arrays scoreArray=[]; dispArray=[]; //reset the values in the form $("#scores").val(""); $("#first_name").val(""); $("#last_name").val(""); $("average_score").val(""); $("#score").val(""); }); //function to sort the scores based on the last name that was entered $("#sort_button").click(function(){ var mylen = scoreArray.length; //sorting for(var kk=0;kk<mylen;kk++) { for(var aa = 1; aa<(mylen-kk);aa++) { var xp1 = dispArray[aa-1].split(" "); var lname1 = xp1[0]; lname1 = lname1.slice(0, -1); var xp2 = dispArray[aa].split(" "); var lname2 = xp2[0]; lname2 = lname2.slice(0, -1); if (lname1 > lname2){ var tp1 = scoreArray[aa]; scoreArray[aa]=scoreArray[aa-1]; scoreArray[aa-1] = tp1; var tp2 = dispArray[aa]; dispArray[aa]=dispArray[aa-1]; dispArray[aa-1] = tp2; } } } //display the scores $("#scores").val(""); var st=" "; for(var i=0;i<dispArray.length;i++) { st += (dispArray[i]+"\n"); } //display the sorted scores $("scores").val(st); }); $("#first_name").focus(); It is giving the error: Uncaught TypeError: Cannot read property 'click' of null at scores.js:38 Line 38 is the click function for the add button to add the score to the display array: $("#add_button").click(function(){ Any thoughts on this?
This is happening because you've defined $ as a function which returns the result of document.getElementById(). What you've not accounted for is what happens if this operation finds no element, in which case it returns null, and there is no .click() method of null. In short: your selector #add_button seems not to be finding the intended element. So check your DOM, and the presence of the element, before running that line. Always suspect your selectors. Either that or build in a check that only goes to .click() on finding an element successfully. let el = $('#add_button'); if (el) el.click(...);
push multivalue elements to array in titanium (JS)
In titanium im accessing the phone book and i have an array with contact information (single and multi-value fields). i have troubles with the multi-value emails field. the multi-value fields stringified objects looks like this (work, home, other as well as multiple emails for work, home, etc): email: {"work":["kate-bell#mac.com","www.icloud.com"]} phone: {"work":["(555) 766-4823"],"other":["(707) 555-1854"]} the code i have so far: function buddy_check(){ var all_emails= []; // array of emails from senders contacts var multiValue = ['email']; var people = Ti.Contacts.getAllPeople(); // gets all contacts (recordId, name,…) for (var i=0, ilen=people.length; i<ilen; i++){ Ti.API.info('---------------------'); var person = people[i]; //for (var j=0, jlen=singleValue.length; j<jlen; j++){ // Ti.API.info(singleValue[j] + ': ' + person[singleValue[j]]); // } for (var j=0, jlen=multiValue.length; j<jlen; j++){ Ti.API.info(multiValue[j] + ': ' + JSON.stringify(person[multiValue[j]])); all_emails.push(); } } i need all emails of the phone book in one array, separated by comma. underscore functions would work as well. what do i have to push to the all_emails array? is there a simpler way to extract the emails and put it in an array (e.g search for "#")? thx for sharing insights! P.S: the user is of course informed that the emails are being checked with our database.
from here: http://www.oodlestechnologies.com/blogs/How-to-extract-contact-list-having-phone-numbers-and-emails-from-iPhone-contacts-using-Titanium var data = []; var people = Ti.Contacts.getAllPeople(); for (var i = 0, ilen = people.length; i < ilen; i++) { var person = people[i]; var title = people[i].fullName; if (!title || title.length === 0) { title = "No Name"; } Ti.API.info("person name : " + title); var personEmails = []; //this check is used for conforming that array will contain at least one email that is actual. var actualConfirmed = false; //fetching emails //Ti.API.info("person email::::1 " + JSON.stringify(person.email)); for (var temp in person.email) { var temp_emails = person.email[temp]; if (temp_emails && (temp_emails.length > 0)) { //Ti.API.info("person email::::2 " + JSON.stringify(temp_emails)); for (var k = 0; k < temp_emails.length; k++) { var temp_email = temp_emails[k]; var isActualEmail = emailValidation(temp_email); Ti.API.info("temp_email " + temp_email + " :::: isActualEmail " + isActualEmail); if (isActualEmail) { actualConfirmed = true; personEmails.push(temp_email); } } } } - See more at: http://www.oodlestechnologies.com/blogs/How-to-extract-contact-list-having-phone-numbers-and-emails-from-iPhone-contacts-using-Titanium#sthash.q7TJF8Lg.dpuf
nested javascript queries in parse
I have the code below. Basically I have 3 nested parse queries. One is getting a number of "followers" and for each follower I am getting a number of "ideas" and for each idea I would like to get that idea creator's name (a user in the user table). The first two nested queries work but then when i try to get the name of the user (the creator of the idea), that last nested query DOES NOT execute in order. That query is skipped, and then it is executed later in the code. Why is this happening please? var iMax = 20; var jMax = 10; var IdeaList = new Array(); var IdeaListCounter = 0; var myuser = Parse.User.current(); var Followers = new Parse.Query("Followers"); Followers.equalTo("Source_User",{__type: "Pointer",className: "_User",objectId: myuser.id}); Followers.find({ success: function(results) { for (var i = 0; i < results.length; i++) { var object = results[i]; var Ideas = new Parse.Query("Ideas"); Ideas.equalTo("objectId_User", {__type: "Pointer",className: "_User",objectId: object.get('Destination_User').id}); Ideas.find({ success: function(results2) { for (i=0;i<iMax;i++) { IdeaList[i]=new Array(); for (j=0;j<jMax;j++) { IdeaList[i][j]=0; } } for (var j = 0; j < results2.length; j++) { var object2 = results2[j]; var ideausername2 = ""; IdeaListCounter++; var ideausername = new Parse.Query("User"); ideausername.equalTo("objectId",object2.get('objectId_User').id); ideausername.first({ success: function(ideausernameresult) { ideausername2 = ideausernameresult.get("name"); } }); IdeaList[IdeaListCounter,0] = object2.get('objectId_User').id + " " + ideausername2; //sourceuser IdeaList[IdeaListCounter,1] = object2.get('IdeaText'); //text IdeaList[IdeaListCounter,2] = object2.get('IdeaImage'); //image IdeaList[IdeaListCounter,3] = object2.get('IdeaLikes'); //likes IdeaList[IdeaListCounter,4] = object2.get('IdeaReport'); //reports
Your nested query is asynchronous. Check out the answer at the following for guidance: Nested queries using javascript in cloud code (Parse.com)
Spliting String and getting appropriate value in JavaScript
I have a string where |||| means next to it is the directory. ||| means the user is allowed to access this directory and || means the files allocated to these users follow. I need to find allocated file names of a specific user from this string. I have tried to split the string and assign values to an array but I am not able to get the result I'm looking for. This is the string: ||||Root|||adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,adil001,km11285c,km61052,km61639c,||LimitTest_20140528164643.xlsx,testTask2_20140528140033.xlsx,||||1400842226669|||adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,||LimitTest_20140528164643.xlsx,testTask2_20140528140033.xlsx,testTask1_20140528135944.xlsx,testTask2_20140528140033.xlsx,||||1401191909489|||adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,||LimitTest_20140528164643.xlsx,testTask2_20140528140033.xlsx,testTask1_20140528135944.xlsx,testTask2_20140528140033.xlsx,LimitTest_20140528164643.xlsx, And here is my attempt: function getData() { var user = 'km11285c'; var value = "||||Root|||adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,adil001,km11285c,km61052,km61639c,||LimitTest_20140528164643.xlsx,testTask2_20140528140033.xlsx,||||1400842226669|||adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,||LimitTest_20140528164643.xlsx,testTask2_20140528140033.xlsx,testTask1_20140528135944.xlsx,testTask2_20140528140033.xlsx,||||1401191909489|||adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,||LimitTest_20140528164643.xlsx,testTask2_20140528140033.xlsx,testTask1_20140528135944.xlsx,testTask2_20140528140033.xlsx,LimitTest_20140528164643.xlsx,"; var users = null; var files = null; var Dir = value.split("||||"); var arrayLength = Dir.length; for (var i = 0; i < arrayLength; i++) { users = Dir[i].split("|||"); } return users; } console.log(getData()); and the jsFiddle
I changed your jsfiddle example a bit so maybe you need to change the code here and there, but something like this should work: function buildTree(data) { var tree = []; var dirs = data.split("||||"); // Remove the first entry in the array, since it should be empty. dirs.splice(0, 1); for (var i = 0; i < dirs.length; ++i) { var tempArray = dirs[i].split("|||"); var dirName = tempArray[0]; var usersAndFiles = tempArray[1]; tempArray = usersAndFiles.split("||"); var users = tempArray[0]; var files = tempArray[1]; var treeDir = { name: dirName }; treeDir.users = users.split(","); treeDir.files = files.split(","); tree.push(treeDir); } return tree; } function getData() { var user = 'km11285c'; var value="||||Root|||adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,adil001,km11285c,km61052,km61639c,||LimitTest_20140528164643.xlsx,testTask2_20140528140033.xlsx,||||1400842226669|||adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,||LimitTest_20140528164643.xlsx,testTask2_20140528140033.xlsx,testTask1_20140528135944.xlsx,testTask2_20140528140033.xlsx,||||1401191909489|||adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,km11285c,km61052,km61639c,adil001,kl04707c,km47389,km58184,km61052,kq61023c,||LimitTest_20140528164643.xlsx,testTask2_20140528140033.xlsx,testTask1_20140528135944.xlsx,testTask2_20140528140033.xlsx,LimitTest_20140528164643.xlsx,"; var tree = buildTree(value); for (var i = 0; i < tree.length; ++i) { var dir = tree[i]; if (dir.users.indexOf(user) >= 0) { console.log("User '" + user + "' has access to directory '" + dir.name + "', which contains these files: " + dir.files.join(",")); } } } getData();
javascript, .split(), implement additional variable
I found a code for city,state dropdown menu. It works flawlessly, but I am implementing additional feature by adding a US State to the following code: //countries array var countries = Object(); countries['Africa'] = '|Algeria|Angola|Benin'; //state array var city_states = Object(); city_states['United States'] = '|Washington DC||Alabama|Alaska'; this is an array for US Cities, but I want to add a State abbreviation like so: DC, AL, AK and so on to be sent to the menu such as this: function setRegions() { for (region in countries) document.write('<option value="' + region + '">' + region + '</option>'); } function set_country(oRegionSel, oCountrySel, oCity_StateSel) { var countryArr; oCountrySel.length = 0; oCity_StateSel.length = 0; var region = oRegionSel.options[oRegionSel.selectedIndex].text; if (countries[region]) { oCountrySel.disabled = false; oCity_StateSel.disabled = true; oCountrySel.options[0] = new Option('SELECT COUNTRY',''); countryArr = countries[region].split('|'); for (var i = 0; i < countryArr.length; i++) oCountrySel.options[i + 1] = new Option(countryArr[i], countryArr[i]); document.getElementById('txtregion').innerHTML = region; document.getElementById('txtplacename').innerHTML = ''; } else oCountrySel.disabled = true; } function set_city_state(oCountrySel, oCity_StateSel) { var city_stateArr; oCity_StateSel.length = 0; var country = oCountrySel.options[oCountrySel.selectedIndex].text; if (city_states[country]) { oCity_StateSel.disabled = false; oCity_StateSel.options[0] = new Option('SELECT NEAREST DIVISION',''); city_stateArr = city_states[country].split('|'); for (var i = 0; i < city_stateArr.length; i++) oCity_StateSel.options[i+1] = new Option(city_stateArr[i],city_stateArr[i]); document.getElementById('txtplacename').innerHTML = country; } else oCity_StateSel.disabled = true; } function print_city_state(oCountrySel, oCity_StateSel) { var country = oCountrySel.options[oCountrySel.selectedIndex].text; var city_state = oCity_StateSel.options[oCity_StateSel.selectedIndex].text; if (city_state && city_states[country].indexOf(city_state) != -1) document.getElementById('txtplacename').innerHTML = city_state + ', ' + country; else document.getElementById('txtplacename').innerHTML = country; } I was thinking adding an additional array of State abbreviations, but I think adding a simple state abbreviation to the already built array would do just fine by adding another value in the setregions() and having + abbreviation + instead of + region +. Any ideas how to implement it? -thank you.
If you have an array of States (objects) rather than an array of Strings you could do something like this: function State(longName, shortName) { this.longName = longName; this.shortName = shortName; } I don't know what the abbreviations are, but store them like this in your array var cityStates = "State:Abbrev|Washington DC:WDC|ETC:etc" var stateNames = cityStates.split("|"); var states = new Array(stateNames.length); for (i=0; i<states.length; i++) var longName = stateNames[i].split(":")[0]; var shortName = stateNames[i].split(":")[1]; states[i] = new State(longName,shortName); } That would give you a new array "states" with 50 state objects, each which could be called like this: states[0] //(returns a State object at index 0) states[0].longName //(returns the long name) states[0].shortName //(returns the abbreviated name)