How to create a constructor function? - javascript

Problem: I'm trying to create a constructor function named Wizard that takes 2 parameters: name and spells, and then create a Wizard object:
"Each particular instance of wizard must have a name property (a string), a spells property which is an array of strings, and a castSpell method capable of returning a random spell in string format."
The object has the following properties: name is "Gorrok" (string), and spells is "abracadabra" and "cadabraabra" (array).
Objective: to invoke the castSpell method to display a random spell like so:
Gorrok : abracadabra
Code: I've only written the following code so far, and I'm stuck at this stage!
function Wizard(name, spells){
this.name = name;
this.spells = [spells];
this.castSpell = function(){
var v = Math.random();
if (v >= 1)
document.write(this.name + " : " + this.spells[0]);
else
document.write(this.name + " : " + this.spells[1]);
}
}
var w = new Wizard("Gorrok", "abracadabra", "cadabraabra");
w.castSpell();

So, Math.random() will return a number between 0 and 1, so it'll never be bigger than 1.
Also, you can't convert the remaining arguments to an array the way you have.
Simplest fix:
function Wizard(name, spells){
this.name = name;
this.spells = spells; // assume spells is already an array
this.castSpell = function(){
var v = Math.random();
if (v >= 0.5)
document.write(this.name + " : " + this.spells[0]);
else
document.write(this.name + " : " + this.spells[1]);
}
}
var w = new Wizard("Gorrok", ["abracadabra", "cadabraabra"]);
w.castSpell();

I think Paul's answer is correct. Also, for functions not in ctor, use prototype. Prototypes also allow you add member vars that are not defined in constructor.
Here's an example from w3:
function Person(first, last, age, eyecolor) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eyecolor;
}
Person.prototype.nationality = "English";

You can also use Math.round() to get 0 or 1 randomly
var v = Math.round( Math.random() );
document.write(this.name + " : " + this.spells[v]);

Related

How to make value auto update when input numbers are changed in javascript?

Here is a link to the tip calculator - it's hosted on netlify.
I created a tip calculator using html, scss, javascript. no tutorials used, so I'm pretty proud. It took me waaayyyyy longer than I had planned on, but it's done. needless to say, I am a complete beginner.
In any event, I need some help.
I need to know how to make the numbers auto-update if I input a new dollar amount into the billing input.
For instance, if the bill is $50, and the tip percent is 50% that's a $25 tip Amount. for a total bill of $75 dollars.
But let's say I mistyped the bill, so I go back to put in $60, 50% of $60 is $30. so the total bill amount should auto-update to $90. But I can't figure out how to get all of that to happen instantaneously when I change the dollar amount in the billing input.
I have a feeling that it has something to do with using a "change" event listener. but I don't understand how to best implement it, or if that's even the answer here.
// Upper Box Selections
const tipPercent = document.querySelector(".tip-percent");
const tipSlider = document.querySelector("#tip-slider");
tipSlider.oninput = function () {
billInput = Number(document.querySelector("#bill-amt").value);
tipPercent.innerHTML = this.value + "%";
//Discovered that number input type still returns a string
//You can wrap multiple variables in parenthesis in order to append methods
let tipAmount = document.querySelector(".tip-amount");
// if a variable is referenced but not defined, it will be added to the window element - can now use in second function
tipTotal = Number((billInput * Number(this.value / 100)).toFixed(2));
tipAmount.innerHTML = "$" + tipTotal.toFixed(2);
const billTotal = document.querySelector(".bill-total");
billForSplit = Number(billInput + tipTotal).toFixed(2);
billTotal.innerHTML =
"<strong>$</strong>" + "<strong>" + billForSplit + "</strong>";
};
// Bottom Box Selections
// -Grab slider value
const splitSlider = document.querySelector("#split-slider");
splitSlider.oninput = function () {
// -Grab split person value-split PERSON for 1, people for more than 1
const splitPeople = document.querySelector(".split-people");
if (splitSlider.value <= 1) {
splitPeople.innerHTML = splitSlider.value + " person";
} else {
splitPeople.innerHTML = splitSlider.value + " people";
}
// -grab tip per person value
const splitTip = document.querySelector(".split-tip");
// -grab total bill per person value
const splitTotal = document.querySelector(".split-total");
// - tip per person equals tipTotal / split slider value
splitTip.innerHTML = "$" + (tipTotal / splitSlider.value).toFixed(2);
// -total bill/person = billTotal / splitSlider.value
splitTotal.innerHTML =
"<strong>$</strong>" +
"<strong>" +
(billForSplit / splitSlider.value).toFixed(2) +
"</strong>";
};
https://wonderful-meninsky-e0b1c7.netlify.app/
You should declare the function with a name like calcTotal() which will be run every time there is an input for the bill and tip:
const tipPercent = document.querySelector(".tip-percent");
const tipSlider = document.querySelector("#tip-slider");
function calcTotal() {
billInput = Number(document.querySelector("#bill-amt").value);
tipPercent.innerHTML = this.value + "%";
//Discovered that number input type still returns a string
//You can wrap multiple variables in parenthesis in order to append methods
let tipAmount = document.querySelector(".tip-amount");
// if a variable is referenced but not defined, it will be added to the window element - can now use in second function
tipTotal = Number((billInput * Number(this.value / 100)).toFixed(2));
tipAmount.innerHTML = "$" + tipTotal.toFixed(2);
const billTotal = document.querySelector(".bill-total");
billForSplit = Number(billInput + tipTotal).toFixed(2);
billTotal.innerHTML =
"<strong>$</strong>" + "<strong>" + billForSplit + "</strong>";
};
tipSlider.oninput = calcTotal;
document.querySelector("#bill-amt").oninput = calcTotal;

assigning values to properties in constructor function

I am working on an assignment for a JS course.
Instructions say to:
setHrs() function takes input of hours worked. function should save input to its instance variable name hrs. no returns.
Okay, so I am pretty much stuck. Below is my code, where I define the employees, give them properties, share those properties between Employee --> Employee1 --> Employee2
Once I got Employees sorted out, I need to create functions within Hourly() that I can then call on later in the program; getRate() and getHrs().
I need pointers for assigning new values to this.rate and this.hrs, here is my code:
//Prince of Wales, Bed and Breakfast Resort
function Employee(id, name, hiredate, position){
this.id = id;
this. name = name;
this.hiredate = hiredate;
this.position = position;
}
Employee.prototype.hired = "Employed";
var Employee1 = new Employee("4", "Blackadder",
"06-03-1902", "butler");
var Employee2 = new Employee("5", "Baldrick",
Employee1.hiredate, "who knows");
// printed out to test prototype works... everything above can be printed
function Hourly(rate, hrs) {
this.rate = 0;
this.hrs = 0;
this.setHrs = function(){
// set Employee1 hours
// set Employee2 hours
}
this.setRate = function() {
// set Employee1 rate
// set Employee2 rate
}
}
Hourly.prototype = new Employee();
Employee1.prototype.hrs = 50;
Employee2.prototype.hrs = 25;
Employee1.prototype.rate = 4;
Employee2.prototype.rate = 1;
console.log(
"Name : " , Employee1.name ,
" Hourly Rate : ", Employee1.setRate(),
" Hours Worked : ", Employee1.setHrs(),
);
console.log(
"Name : " , Employee2.name ,
" Hourly Rate : ", Employee2.setRate(),
" Hours Worked : ", Employee2.setHrs(),
);
I had thought of something else that might work, it doesn't, but I might be on the right track???
this.setHrs = function(){
Employee1.hrs = 50;
Employee2.hrs = 25;
}
this.setRate = function() {
Employee1.rate = 4;
Employee2.rate = 1;
}
I have also thought, maybe rate and hrs should be arrays and I can push values to them, but everything I tried didn't add values to the arrays. Plus, if I do that then when I print the array, I could end up with more problems.
SOLUTION EDIT:
function Employee(id, name){
this.id = id;
this. name = name;
} // end of employee()
function Hourly(id, name) {
Employee.call(this,id,name);
var rate = 0.0;
var hrs = 0.0;
this.setHrs = function(time){ this.hrs = time; }
this.setRate = function(pay) { this.rate = pay; }
My main issue may have been the variable set up. I thought I understood prototypal inheritance, but there were some small details that caused my code not to run. once I changed my code from
this.rate = rate;
this.hrs = hrs;
to
var rate = 0;
var hrs = 0;
it was pretty much smooth sailing from there. Additionally, I needed to call the previous function Employee(). My employee variables are defined later in the code, but they are pretty much set up the same except for one important change... calling the appropriate function.
var Employee1 = new Hourly("2262124", "Blackadder");
Employee1.setHrs(50);
Employee1.setRate(4);
Employee1.getPayCheck();
console.log(" ");
previously, I called Employee() and that worked for assigning values to name and id, but not for setting rate and hrs which are "further" down in the inheritance chain. I then passed ALL properties to the Hourly() function and my properties were getting their values appropriately.
I just wanted to post my solution here for others who may be having issues practicing with inheritance. Thank you for reading and commenting!!
The setter functions need to take a parameter, and then they should update the property of the current object.
this.setHrs = function(hours){
this.hrs = hours;
}
You shouldn't be accessing Employee1 and Employee2 inside the methods, because the caller can have other variables containing these objects. They use those variables when they call the method:
Employee1.setHrs(50);
Employee2.setHrs(25);
Part of the problem was also the , instead of + in console.log
function Employee(id, name, hiredate, position){
this.id = id;
this. name = name;
this.hiredate = hiredate;
this.position = position;
this.rate = 0;
this.hours = 0;
}
Employee.prototype.hired = "Employed";
Employee.prototype.setHrs =function(hours){
this.hours = hours;
}
Employee.prototype.setRates = function(rates){
this.rate = rates;
}
function Hourly (employeeContext,hours, rates){
employeeContext.setRates(rates);
employeeContext.setHrs(hours);
}
var Employee1 = new Employee("4", "Blackadder",
"06-03-1902", "butler");
var Employee2 = new Employee("5", "Baldrick",
Employee1.hiredate, "who knows");
//calling Hourly
Hourly(Employee1, 4, 25);
Hourly(Employee2, 1, 50);
console.log(
"Name : " + Employee1.name +
" Hourly Rate : " + Employee1.rate+
" Hours Worked : "+ Employee1.hours
);
console.log(
"Name : " + Employee2.name +
" Hourly Rate : "+ Employee2.rate+
" Hours Worked : "+ Employee2.hours
);
setHrs() function takes input of hours worked
You should be passing some parameters to this function. A setter function will take some input and "set" some object variable.
setHrs(hours)
You should do this for each Employee object you create.
Employee1.setHrs(50)
Employee2.setHrs(25)
I hope this helped.
EDIT
For clarification on what I meant:
If you were to create an Employee object like you already have
function Employee(id, name, hiredate, position){
this.id = id;
this.name = name;
this.hiredate = hiredate;
this.position = position;
}
Then you can instantiate Employees like this:
var johnSmith = new Employee("6", "John Smith", "04-28-2017", "Chef")
Now if we were to modify the Employee class like this:
function Employee(id, name, hiredate, position){
this.id = id;
this.name = name;
this.hiredate = hiredate;
this.position = position;
this.setHrs = function (hours) = {
this.hours = hours;
}
this.setRate = function (rate) = {
this.rate = rate;
}
}
Now you are able to instantiate the Employee object like I did above but now you can call these member functions on the object.
johnSmith.setHours(50);
johnSmith.setRate(25);
There are ways to handle inheritance if you had to handle both salary and hourly workers, but this solution will work if you only have to deal with hourly workers.

Declaring variable within functions

Ok, so I I'm having this strange behaviour that I cannot explain. Look at the following:
$("#completeData").on("click", function() {
var toUpdate = {};
var toUpdateCount = 0;
var ratios = {};
This.calculateGradePerSize();
//1) Select all sizes that are equal to NA or are Equal to 0 (means its a new one)
$.each(This.logements, function(key, l) {
if (l.sizeMyId === "NA" || l.sizeMyId === 0) {
toUpdate[l.rueNum] = l;
toUpdateCount++;
} else { //else init the ratios because it means they are actually present
/**
//My problem is this variable,
I want it to be equal to an empty object
But for reasons I cannot seem to understand,
it takes in account the latter modification in the code
that happens to this variables
*/
ratios[l.sizeMyId] = {};
}
});
console.log(toUpdate);
console.log(ratios);
console.log(This.sizeRatio);
//2) Calculate Ratios and build the ratios function of the toUpdate
$.each(This.sizeRatio, function(sizeMyId, count) {
if (sizeMyId !== "NA" && sizeMyId != 0) {
console.log("COUNT SIZE: " + count + " COUNT LOGEMENT: " + This.countLogement + " toUpdateCount: " + toUpdateCount + " SizeMyId: " + sizeMyId);
console.log("Calculation: " + count / This.countLogement * toUpdateCount);
ratios[sizeMyId].count = Math.ceil(count / This.countLogement * toUpdateCount);
console.log("Calculation WITH CEIL: " + Math.ceil(count / This.countLogement * toUpdateCount));
ratios[sizeMyId].grade = This.sizeGrade[sizeMyId];
ratios[sizeMyId].sizeMyId = sizeMyId;
}
});
console.log(ratios);
});
As explained in the multiline comment, my problem is the ratio variable. I tried declaring the variable without var prefix, so that JS doesn't know its existence but still, I want it to be empty object. In fact, the problem has stronger roots than simply that, I cannot update it. Each change I make to the ratios var are not registered, but I wanna start with the beginning how can I make sure that this variable is empty at the beginning of the function.
I don't know if this question is really worth. Thinking about deleting it. My bug was that the count variable in the each function as well as the ratio definition were the same hence not registering.
As for the variable not being an empty one at function start. It simply how the JS engine works. If there is something not working, more likely than not, there is something wrong in your code.
$.each(This.sizeRatio, function (sizeMyId, count) {
if (sizeMyId !== "NA" && sizeMyId != 0) {
console.log("COUNT SIZE: " + count + " COUNT LOGEMENT: " + This.countLogement + " toUpdateCount: " + toUpdateCount + " SizeMyId: " + sizeMyId);
console.log("Calculation: " + count / This.countLogement * toUpdateCount);
//HERE ratios[sizeMyId].count IS THE SAME than the anonymous function.
ratios[sizeMyId].count = Math.ceil(count / This.countLogement * toUpdateCount);
console.log("Calculation WITH CEIL: " + Math.ceil(count / This.countLogement * toUpdateCount));
ratios[sizeMyId].grade = This.sizeGrade[sizeMyId];
ratios[sizeMyId].sizeMyId = sizeMyId;
}
});

Do JavaScript class instances clone functions?

If i have the following code:
function myClass(){
this.type = 1;
this.ret = function(){
return this.type;
}
}
var ins1 = new myClass,
ins2 = new myClass,
ins3 = new myClass;
ins2.type = 2;
ins3.type = 3;
console.log(ins1.ret() + ' - ' + ins2.ret() + ' - ' + ins3.ret());
The output in the console is
1 - 2 - 3
When the code runs (the console.log() part), is there one method ret() running, or three? If each instance creates a new method, how can I avoid that? If they all do the same exact thing, why have three of them.
The methods are different indeed. You are wasting memory.
ins1.ret == ins2.ret; // false
Instead, you can define the method in the prototype:
function myClass(){}
myClass.prototype.type = 1;
myClass.prototype.ret = function(){
return this.type;
};

Unordered function arguments in Javascript

Below is a regular function with named parameters:
function who(name, age, isMale, weight)
{
alert(name + ' (' + (isMale ? 'male' : 'female') + '), ' + age + ' years old, ' + weight + ' kg.');
}
who('Jack', 30, true, 90); //this is OK.
What I want to achive is; whether you pass the arguments in order or not; the function should produce a similar result (if not the same):
who('Jack', 30, true, 90); //should produce the same result with the regular function
who(30, 90, true, 'Jack'); //should produce the same result
who(true, 30, 'Jack', 90); //should produce the same result
This enables you to pass a list of arguments in any order but still will be mapped to a logical order. My approach up to now is something like this:
function who()
{
var name = getStringInArgs(arguments, 0); //gets first string in arguments
var isMale = getBooleanInArgs(arguments, 0); //gets first boolean in arguments
var age = getNumberInArgs(arguments, 0); //gets first number in arguments
var weight = getNumberInArgs(arguments, 1); //gets second number in arguments
alert(name + ' (' + (isMale ? 'male' : 'female') + '), ' + age + ' years old, ' + weight + ' kg.');
}
There is a little problem here; functions such as getStringInArgs() and getNumberInArgs() go through all the arguments each time to find the arg by type at the specified position. I could iterate through args only once and keep flags for the positions but then I would have to do it inside the who() function.
Do you think this approach is logical and the only way? Is there a better way to do it?
EDIT 1: Code above actually works. I just want to know if there is a better way.
EDIT 2: You may wonder if this is necessary or whether it makes sense. The main reason is: I'm writing a jQuery function which adds a specific style to a DOM element. I want this function to treat its arguments like shorthand CSS values.
Example:
border: 1px solid red;
border: solid 1px red; /*will produce the same*/
So; here is the real and final code upto now:
(function($){
function getArgument(args, type, occurrence, defaultValue)
{
if (args.length == 0) return defaultValue;
var count = 0;
for(var i = 0; i < args.length; i++)
{
if (typeof args[i] === type)
{
if (count == occurrence) { return args[i]; }
else { count++; }
}
}
return defaultValue;
}
$.fn.shadow = function()
{
var blur = getArgument(arguments, 'number', 0, 3);
var hLength = getArgument(arguments, 'number', 1, 0);
var vLength = getArgument(arguments, 'number', 2, 0);
var color = getArgument(arguments, 'string', 0, '#000');
var inset = getArgument(arguments, 'boolean', 0, false);
var strInset = inset ? 'inset ' : '';
var sValue = strInset + hLength + 'px ' + vLength + 'px ' + blur + 'px ' + color;
var style = {
'-moz-box-shadow': sValue,
'-webkit-box-shadow': sValue,
'box-shadow': sValue
};
return this.each(function()
{
$(this).css(style);
});
}
})(jQuery);
Usage:
$('.dropShadow').shadow(true, 3, 3, 5, '#FF0000');
$('.dropShadow').shadow(3, 3, 5, '#FF0000', true);
$('.dropShadow').shadow();
I find using objects to be more straight-forward and less error prone in the future:
var person = {
name: 'Jack',
age: 30,
isMale: true,
weight: 90
};
who(person);
function who(person){
alert(person.name +
' (' + (person.isMale ? 'male' : 'female') + '), ' +
person.age + ' years old, ' +
person.weight + ' kg.');
}
That way when you come back years later you don't have to lookup to see if age was the first, second, or fifth number and is more descriptive of what you are trying to accomplish.
This seems unnecessarily complex, not just from the perspective of the function, which needs to reorder its arguments, but also from the perspective of whoever is calling. You say that the function can accept its paramters in any order, but that's not entirely true. Since your determination of which variable is which is based on type, it relies on each variable being a different type. The name and gender can be anywhere, but the numeric arguments have to be in a specific order. It also prevents someone from passing in "30" or "90", which are numbers but will be regarded as strings - confusing it with the name and not finding an age or weight.
You can cache the arguments of a specific type in the arguments array. This is a big hack, you could follow the same pattern with the other getTypeInArgs
function getNumberInArgs(args, index) {
if (!args.numbers) {
args.numbers = [];
for (var i=0; i < args.length; i++) {
// You have to implement isNumber
if ( isNumber (args[i]) ) {
args.numbers.push(args[i];
}
}
}
return args.numbers[index];
}
I've never heard of accepting arguments in any order, except for the implode function in PHP, and it's marked on its documentation page as a big hack for historical reasons. So I wouldn't do this. If the order is too confusing, I would use the approach of taking a literal object, as suggested by WSkid.
You could try copying the arguments array into something you can destructively update:
untested code: Edit: I think it works now.
function args_getter(their_arguments){
//copy arguments object into an actual array
//so we can use array methods on it:
var arr = Array.prototype.slice.call(their_arguments);
return function(type){
var arg;
for(var i=0; i<arr.length; i++){
arg = arr[i];
if(type == typeof arg){
arr.slice(i, 1);
return arg;
}
}
return "do some error handling here"
}
}
function foo(){
var args = args_getter(arguments);
var b1 = args('boolean');
var b2 = args('boolean');
var n1 = args('number');
console.log(n1, b1, b2);
}
//all of
// foo(1, true, false),
// foo(true, 1, false), and
// foo(true, false, 1)
// should print (1, true, false)
This is still O(N^2) since you go through the array every time. However this shouldn't be an issue unless your functions can receive hundreds of arguments.
I agree with Griffin. This cannot be done unless you limit the choices more than you have. As it is, you have a string, a boolean and two numbers. Without some more rules on what can be in what position, you cannot tell which number is which. If you're willing to make some rule about which number comes first or which number comes after some other argument, then you can sort it out. In general, I think this is a bad idea. It's much better (from the standpoint of good programming) to use an object like WSkid suggested.
Anyway, if you wanted to make a rule like the weight has to come after the age, then it could be done like this:
function findParm(args, type) {
for (var i = 0; i < args.length; i++) {
if (typeof args[i] == type) {
return(i);
}
}
return(-1);
}
function who(name, age, isMale, weight) {
// assumes all variables have been passed with the right type
// age is before weight, but others can be in any order
var _name, _age, _isMale, _weight, i;
var args = Array.prototype.slice.call(arguments);
_name = args[findParm(args, "string")]; // only string parameter
_isMale = args[findParm(args, "boolean")]; // only boolean parameter
i = findParm(args, "number"); // first number parameter
_age = args[i];
args.splice(i, 1); // get rid of first number
_weight = args[findParm(args, "number")]; // second number parameter
// you now have the properly ordered parameters in the four local variables
// _name, _age, _isMale, _weight
}
who("fred", 50, false, 100);
Working here in this fiddle: http://jsfiddle.net/jfriend00/GP9cW/.
What I would suggest is better programming is something like this:
function who(items) {
console.log(items.name);
console.log(items.age);
console.log(items.weight);
console.log(items.isMale);
}
who({name: "Ted", age: 52, weight: 100, isMale: true});
function who({name, age, isMale, weight})
{
alert(name + ' (' + (isMale ? 'male' : 'female') + '), ' + age + ' years old, ' + weight + ' kg.');
}
who({name:'Jack', age:30, isMale:true, weight90});
Taking parameters as an object, allows you to pass arguments in any order.
There is no way you can do this since you have arguments of the same type.
Unless age and weight have non-overlapping ranges, you can't do this. How are you supposed to distinguish between 30 and 60 for weight or age??
This code:
function who(items) { console.log(items.name); console.log(items.age); console.log(items.weight); console.log(items.isMale);}who({name: "Ted", age: 52, weight: 100, isMale: true});
That a previous posted sent seems sensible. But why make things complicated. My experience when people make things complicated things go wrong.
BTW - The solution above (as the previous posted gave) is similar to the Perl solution.

Categories

Resources