javascript check if function parameter meets a condition - javascript

I'm trying to write a basic "quest" function and have run into a few issues.
This is what I've done so far
JS
var clicks = 0;
var coins = 10;
var Quests = function(type, required, reward, message) {
this.type = type;
this.required = required;
this.reward = reward;
this.message = message;
this.quest = function() {
if (this.type >= this.required) {
coins += this.reward;
return this.message;
} else {
alert('You don\'t have enough ' + required);
}
};
};
quest1 = new Quests(clicks, 10, 50, 'You completed this quest!');
quest2 = new Quests(clicks, 5, 50, 'You completed this quest!');
theQuests = [quest1, quest2];
$(document).ready(function() {
$('#click').click(function() {
clicks += 1;
$('#queststuff').text(clicks);
});
$('#quest').click(function() {
$('#queststuff').html(Quests[Math.floor(Math.random() * 2)].quest());
});
});
HTML
<button id="quest">quest</button>
<button id="click">click me!</button>
<div id="queststuff">
</div>
Eventually I'll be using something other than clicks, but for now I wanted to just get the basic function working. I'm fairly new to functions, but at the moment when clicking 'quest' nothing happens, while I'm wanting the alert to display. I've obviously gone wrong somewhere in my code. Is anyone able to point me in the right direction?

You are assigning clicks, an immutable Number, to this.type. Its value is 0 initially, and this.type thus stays 0 after the assignment. You should compare clicks to this.required within the quest method.
Here's a jsFiddle fork version using data-attributes

Your code has several issues.
When you create a new object quest1 with new Objects you pass clicks into it. At the time you pass clicks it equals to 0. And inside that object method quest1.quest(), you check if clicks (this.type) is greater/equal to required clicks (this.required). this.type equals to 0. It will never return true. Since you're creating multiple quests, clicks should be specific to these quests (objects). It shouldn't be global.
Another one is in this line:
$('#queststuff').html(Quests[Math.floor(Math.random() * 2)].quest());
I believe you wanted to address to your theQuests array. Quests[Math.floor(Math.random() * 2)].quest() simply won't work. Even if you change your code to theQuests[0].quest() or quest1.quest() it still won't work. See above, and since there are multiple quests, first you need to check which one is active.
You should review your code. Here's something to get you started. Of course, it needs further improvement and styling. FIDDLE
var Quest = function (requiredClicks, reward, message) {
this.clicks = 0;
this.coins = 0;
this.requiredClicks = requiredClicks;
this.reward = reward;
this.message = message;
this.check = function () {
if (this.clicks >= this.requiredClicks) {
this.coins += this.reward;
return this.message;
} else {
alert('You don\'t have enough clicks: ' + this.clicks + "/" + this.requiredClicks);
}
};
this.click = function() {
this.clicks++;
};
};
var quest1 = new Quest(10, 50, 'You completed this quest!');
$('#click').click(function () {
quest1.click();
$('#queststuff').text(quest1.clicks);
});
$('#quest').click(function () {
$('#queststuff').html(quest1.check());
});

Related

JavaScript - Issues recovering a map in an object after being saved in localStorage

I've been dealing with this for some time. I've a list of sections in which the user checks some checkboxes and that is sent to the server via AJAX. However, since the user can return to previous sections, I'm using some objects of mine to store some things the user has done (if he/she already finished working in that section, which checkboxes checked, etc). I'm doing this to not overload the database and only send new requests to store information if the user effectively changes a previous checkbox, not if he just starts clicking "Save" randomly. I'm using objects to see the sections of the page, and storing the previous state of the checkboxes in a Map. Here's my "supervisor":
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
var children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children().length;
for (var i = 0; i < children; i++) {
if (i % 2 == 0) {
var checkbox = $("#ContentPlaceHolder1_checkboxes_div_" + id).children()[i];
var idCheck = checkbox.id.split("_")[2];
this.selections.set(idCheck, false);
}
}
console.log("Length " + this.selections.size);
this.change = false;
}
The console.log gives me the expected output, so I assume my Map is created and initialized correctly. Since the session of the user can expire before he finishes his work, or he can close his browser by accident, I'm storing this object using local storage, so I can change the page accordingly to what he has done should anything happen. Here are my functions:
function setObj(id, supervisor) {
localStorage.setItem(id, JSON.stringify(supervisor));
}
function getObj(key) {
var supervisor = JSON.parse(localStorage.getItem(key));
return supervisor;
}
So, I'm trying to add to the record whenever an user clicks in a checkbox. And this is where the problem happens. Here's the function:
function checkboxClicked(idCbx) {
var idSection = $("#ContentPlaceHolder1_hdnActualField").val();
var supervisor = getObj(idSection);
console.log(typeof (supervisor)); //Returns object, everythings fine
console.log(typeof (supervisor.change)); //Returns boolean
supervisor.change = true;
var idCheck = idCbx.split("_")[2]; //I just want a part of the name
console.log(typeof(supervisor.selections)); //Prints object
console.log("Length " + supervisor.selections.size); //Undefined!
supervisor.selections.set(idCheck, true); //Error! Note: The true is just for testing purposes
setObj(idSection, supervisor);
}
What am I doing wrong? Thanks!
Please look at this example, I removed the jquery id discovery for clarity. You'll need to adapt this to meet your needs but it should get you mostly there.
const mapToJSON = (map) => [...map];
const mapFromJSON = (json) => new Map(json);
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
this.change = false;
this.selections.set('blah', 'hello');
}
Supervisor.from = function (data) {
const id = data.id;
const supervisor = new Supervisor(id);
supervisor.verif = data.verif;
supervisor.selections = new Map(data.selections);
return supervisor;
};
Supervisor.prototype.toJSON = function() {
return {
id: this.id,
verif: this.verif,
selections: mapToJSON(this.selections)
}
}
const expected = new Supervisor(1);
console.log(expected);
const json = JSON.stringify(expected);
const actual = Supervisor.from(JSON.parse(json));
console.log(actual);
If you cant use the spread operation in 'mapToJSON' you could loop and push.
const mapToJSON = (map) => {
const result = [];
for (let entry of map.entries()) {
result.push(entry);
}
return result;
}
Really the only thing id change is have the constructor do less, just accept values, assign with minimal fiddling, and have a factory query the dom and populate the constructor with values. Maybe something like fromDOM() or something. This will make Supervisor more flexible and easier to test.
function Supervisor(options) {
this.id = options.id;
this.verif = null;
this.selections = options.selections || new Map();
this.change = false;
}
Supervisor.fromDOM = function(id) {
const selections = new Map();
const children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children();
for (var i = 0; i < children.length; i++) {
if (i % 2 == 0) {
var checkbox = children[i];
var idCheck = checkbox.id.split("_")[2];
selections.set(idCheck, false);
}
}
return new Supervisor({ id: id, selections: selections });
};
console.log(Supervisor.fromDOM(2));
You can keep going and have another method that tries to parse a Supervisor from localStorageand default to the dom based factory if the localStorage one returns null.

getCreators() Type Error

I wrote the following function to try to get the user that created the gCal event.
To test this I created an event on calendar and tried to run the script. When the script executes it gets error: "TypeError: Cannot find function getCreators in object CalendarEvent. (line 68, file "Code")".
Any ideas why this is happening? I'm fairly certain getCreators() is a function in object CalendarEvent (Ref:https://developers.google.com/apps-script/reference/calendar/calendar-event#getCreators())
function getuser(instr) {
var today = new Date();
var scriptProperties=PropertiesService.getScriptProperties();
var instrcal= scriptProperties.getProperty(instr);
var event=CalendarApp.getCalendarById(instrcal).getEventsForDay(today);
if (event<1) {
var user= 'None'
}
else if (event>1) {
var user= 'Multiple Users'
}
else {
var user= event.getCreators()
}
return user
}
getEventsForDay returns an array of CalendarEvent objects, all events for that day, even if it is just one.
You'll need to go through the events to find the one you want and then call getCreators().
You can verify this by using
var event=CalendarApp.getCalendarById(instrcal).getEventsForDay(today)[0];
Here the call will work.
The method getEventsForDay(date) gets an array all events that occur on a given day.
Check that you are doing: event < 1 instead of event.length < 1.
Code:
function getuser(instr) {
var today = new Date(),
scriptProperties = PropertiesService.getScriptProperties(),
instrcal = scriptProperties.getProperty(instr),
events = CalendarApp.getCalendarById(instrcal).getEventsForDay(today),
user = 'None';
if (events.length === 1) {
user = events[0].getCreators();
} else if (events.length > 1) {
user = 'Multiple Users';
}
return user;
}

Javascript - Run functions in order (not at same time)

I have a webpage the currently types text# (automated with code below) into a text box ta#.
Problem #1) When I run the function, I can only figure out how to make it type into all textboxes at the same time. -- I want it to type naturally (1 by 1, with a slight delay between each) into each textbox. How can I accomplish this?
Problem #2) What is the proper way to shut off/clear this automated typing once the user clicks/selects a text box?
var text1 = "Type this into textbox usr";
var text2 = "Type this into textbox usr2";
var ta1 = document.getElementById("usr");
var ta2 = document.getElementById("usr2");
function type(string,element){
(function writer(i){
if(string.length <= i++){
element.value = string;
return;
}
element.value = string.substring(0,i);
if( element.value[element.value.length-1] != " " )element.focus();
var rand = Math.floor(Math.random() * (100)) + 140;
setTimeout(function(){writer(i);},rand);
})(0)
}
type(text1,ta1);
type(text2,ta2); // This doesnt work right.
setTimeout makes this inherently asynchronous so the Promise object is your friend. Note that the following code will work in all modern browsers. And that excludes Internet Explorer :).
Here we make type return a Promise that will be resolved when the typing has completed. I've modified your code slightly such that writer returns false when the typing is complete, and if it has, then the code will resolve the Promise, and stop running the timeout. There are a few ways of doing this, but this is what I had time for...
I've also added quick-and-dirty code that will stop the typing when you click into a textbox. However, note that it stops all typing for both textboxes. You can play around with the code to get it to keep going to the second textbox if that's what you want.
var text1 = "Type this into textbox usr";
var text2 = "Type this into textbox usr2";
var ta1 = document.getElementById("usr");
var ta2 = document.getElementById("usr2");
function type(string, element) {
var timeout;
element.addEventListener('click', function() {
if (timeout) {
clearTimeout(timeout);
}
});
var completePromise = new Promise(function(resolve, reject) {
(function writer(i) {
if (string.length <= i++) {
element.value = string;
return false;
}
element.value = string.substring(0, i);
if (element.value[element.value.length - 1] != " ") {
element.focus();
}
var rand = Math.floor(Math.random() * (100)) + 140;
timeout = setTimeout(function() {
if (!writer(i)) {
resolve();
clearTimeout(timeout);
}
}, rand);
return true;
})(0);
});
return completePromise;
}
type(text1, ta1).then(function() {
type(text2, ta2);
});
<input id="usr" type="text" />
<input id="usr2" type="text" />

Reset timeout on event with RxJS

I'm experimenting with RxJS (with the JQuery extension) and I'm trying to solve the following use case:
Given that I have two buttons (A & B) I'd like to print a message if a certain "secret combination" is clicked within a given timeframe. For example the "secret combination" could be to click "ABBABA" within 5 seconds. If the combination is not entered within 5 seconds a timeout message should be displayed. This is what I currently have:
var secretCombination = "ABBABA";
var buttonA = $("#button-a").clickAsObservable().map(function () { return "A"; });
var buttonB = $("#button-b").clickAsObservable().map(function () { return "B"; });
var bothButtons = Rx.Observable.merge(buttonA, buttonB);
var outputDiv = $("#output");
bothButtons.do(function (buttonName) {
outputDiv.append(buttonName);
}).bufferWithTimeOrCount(5000, 6).map(function (combination) {
return combination.reduce(function (combination, buttonName) {
return combination + buttonName;
}, "");
}).map(function (combination) {
return combination === secretCombination;
}).subscribe(function (successfulCombination) {
if (successfulCombination) {
outputDiv.html("Combination unlocked!");
} else {
outputDiv.html("You're not fast enough, try again!");
}
});
While this works fairly well it's not exactly what I want. I need the bufferWithTimeOrCount to be reset when button A is pressed for the first time in a new timeframe. What I'm looking for is that as soon as the secret combination is pressed (ABBABA) I'd like "Combination unlocked!" to be shown (I don't want to wait for the time window to be expired).
Throttle is the typical operator for the delaying with reactive resetting effect you want.
Here's how you can use throttle in combination with scan to gather the combination inputted before the 5 seconds of silence:
var evaluationStream = bothButtons
.merge(bothButtons.throttle(5000).map(function(){return "reset";})) // (2) and (3)
.scan(function(acc, x) { // (1)
if (x === "reset") return "";
var newAcc = acc + x;
if (newAcc.length > secretCombination.length) {
return newAcc.substr(newAcc.length - secretCombination.length);
}
else {
return newAcc;
}
})
.map(function(combination) {
return combination === secretCombination;
});
var wrongStream = evaluationStream
.throttle(5000)
.filter(function(result) { return result === false; });
var correctStream = evaluationStream
.filter(function(result) { return result === true; });
wrongStream.subscribe(function() {
outputDiv.html("Too slow or wrong!");
});
correctStream.subscribe(function() {
outputDiv.html("Combination unlocked!");
});
(1) We scan to concatenate the input characters. (2) Throttle waits for 5 seconds of event silence and emits the last event before that silence. In other words, it's similar to delay, except it resets the inner timer when a new event is seen on the source Observable. We need to reset the scan's concatenation (1), so we just map the same throttled Observable to "reset" flags (3), which the scan will interpret as clearing the accumulator (acc).
And here's a JSFiddle.

How do I get the gender from a particular user when updating a different table? Azure mobile services

I have a table called Subscription and another table called Client I need the gender of the Client who owns the subscription every time I make an update. Here's my update script:
function update(item, user, request) {
var subscriptionId = item.id;
var subscriptionActivitiesTable = tables.getTable("SubscriptionActivity");
var userTable = tables.getTable("User");
var activityTable = tables.getTable("Activity");
var userGender = userTable.where({id: item.UserId}).select('Gender').take(1).read();
console.log(userGender);
activityTable.where({PlanId:item.PlanId, Difficulty: item.Difficulty}).read({
success: function(results){
var startDate = item.StartDate;
results.forEach(function(activity)
{
var testDate = new Date(startDate.getFullYear(),startDate.getMonth(), startDate.getDate());
testDate.setDate(testDate.getDate() + activity.Sequence + (activity.Week*7));
subscriptionActivitiesTable.insert({SubscriptionId: subscriptionId,
ActivityId: activity.id, ShowDate: new Date(testDate.getFullYear(),
testDate.getMonth(), testDate.getDate()), CreationDate: new Date()});
})
}
});
var planWeeks = 12;//VER DE DONDE SACAMOS ESTE NUMERO
var idealWeight = 0;
if (userGender === "Male")
{
idealWeight = (21.7 * Math.pow(parseInt(item.Height)/100,2));
}
else
{
idealWeight = (23 * Math.pow(parseInt(item.Height)/100,2));
}
var metabolismoBasal = idealWeight * 0.95 * 24;
var ADE = 0.1 * metabolismoBasal;
var activityFactor;
if (item.Difficulty === "Easy")
{
activityFactor = 1.25;
}
else if(item.Difficulty === "Medium")
{
activityFactor = 1.5;
}
else
{
activityFactor = 1.75;
}
var caloricRequirement = ((metabolismoBasal + ADE)*activityFactor);
activityTable.where(function(item, caloricRequirement){
return this.PlanId === item.PlanId && this.Type != "Sport" &&
this.CaloricRequirementMin <= caloricRequirement &&
this.CaloricRequirementMax >= caloricRequirement;}, item, caloricRequirement).read({
success: function(results)
{
var startDate = item.StartDate;
results.forEach(function(activity)
{
for (var i=0;i<planWeeks;i++)
{
var testDate = new Date(startDate.getFullYear(),startDate.getMonth(), startDate.getDate());
testDate.setDate(testDate.getDate() + activity.Sequence + (i*7));
subscriptionActivitiesTable.insert({SubscriptionId: subscriptionId,
ActivityId: activity.id, ShowDate: new Date(testDate.getFullYear(),
testDate.getMonth(), testDate.getDate()), CreationDate: new Date()});
}
})
}
})
request.execute();
}
I tried the code above and clientGender is undefined. As you can see I want to use the gender to set the idealWeight.
The read() method expects a function to be passed in on the success parameter - it doesn't return the result of the query like you'd think.
Try something like this instead:
function update(item, user, request) {
var clientTable = tables.getTable("Client");
var clientGender = 'DEFAULT';
clientTable.where({id: item.ClientId}).select('Gender').take(1).read({
success: function(clients) {
if (clients.length == 0) {
console.error('Unable to find client for id ' + item.ClientId);
} else {
var client = client[0];
clientGender = client.Gender;
// since we're inside the success function, we can continue to
// use the clientGender as it will reflect the correct value
// as retrieved from the database
console.log('INSIDE: ' + clientGender);
}
}
});
// this is going to get called while the clientTable query above is
// still running and will most likely show a value of DEFAULT
console.log('OUTSIDE: ' + clientGender);
}
In this sample, the client table query is kicked off, with a callback function provided in the success parameter. When the query is finished, the callback function is called, and the resulting data is displayed to the log. Meanwhile - while the query is still running, that is - the next statement after the where/take/select/read fluent code is run, another console.log statment is executed to show the value of the clientGender field outside the read function. This code will run while the read statement is still waiting on the database. Your output should look something like this in the WAMS log:
* INSIDE: Male
* OUTSIDE: Default
Since the log shows the oldest entries at the bottom, you can see that the OUTSIDE log entry was written sometime before the INSIDE log.
If you're not used to async or functional programming, this might look weird, but as far as I've found, this is now node works. Functions nested in functions nested in functions can get kind of scary, but if you plan ahead, it probably won't be too bad :-)

Categories

Resources