# Intermediate JavaScript Basics - Classes to Modules

## 1. Intro to Classes

```javascript
//Example: Template for dog behaviour
class Dog {
  constructor(name) {
    this._name = name;
    this._behavior = 0;
  }

  get name() {
    return this._name;
  }
  get behavior() {
    return this._behavior;
  }   

  incrementBehavior() {
    this._behavior ++;
  }
}
const halley = new Dog('Halley');
console.log(halley.name); // Print name value to console
console.log(halley.behavior); // Print behavior value to console
halley.incrementBehavior(); // Add one to behavior
console.log(halley.name); // Print name value to console
console.log(halley.behavior); // Print behavior value to console
```

* you created a class called `Dog`, and used it to produce a `Dog` object.
* there are similarities between class and object syntax, but JavaScript calls the `constructor()` method every time it creates a new _instance_ of a class.

### &#x20;# Explanation of the syntax

```javascript
class Dog {
  constructor(name) {
    this.name = name;
    this.behavior = 0;
  }
}
```

* `Dog` is the name of our class. By convention, we capitalize and CamelCase class names.
* JavaScript will invoke the `constructor()` method every time we create a new instance of our `Dog` class.
* This `constructor()` method accepts one argument, `name`.
* Inside of the `constructor()` method, we use the `this` keyword. In the context of a class, `this` refers to an instance of that class. In the `Dog` class, we use `this` to set the value of the Dog instance’s `name` property to the `name` argument.
* Under `this.name`, we create a property called `behavior`, which will keep track of the number of times a dog misbehaves. The `behavior` property is always initialized to zero.

## 2. Instances

Now, we’re ready to create class instances. An _instance_ is an object that contains the property names and methods of a class, but with unique property values.

```javascript
class Dog {
  constructor(name) {
    this.name = name;
    this.behavior = 0;
  } 
}
const halley = new Dog('Halley'); // Create new Dog instance
console.log(halley.name); // Log the name value saved to halley               
// Output: 'Halley'
```

Below our `Dog` class, we use the `new` keyword to create an instance of our `Dog` class.

* store the instance in a variable `halley` that will store an instance of our `Dog` class.
* We use the `new` keyword to generate a new instance of the `Dog` class. The `new` keyword calls the `constructor()`, runs the code inside of it, and then returns the new instance.
* We pass the `'Halley'` string to the `Dog` constructor, which sets the `name` property to `'Halley'`.
* Finally, we log the value saved to the `name` key in our `halley` object, which logs `'Halley'` to the console.

```java
///Example: calling instances
class Surgeon {
  constructor(name, department) {
    this.name = name;
    this.department = department;
  }
}

const surgeonRomero = new Surgeon('Francisco Romero', 'Cardiovascular');
const surgeonJackson = new Surgeon('Ruth Jackson', 'Orthopedics')
```

## 3. Methods

Class method and getter syntax is the same as it is for objects **except you can not include commas between methods**

```javascript
class Dog {
  constructor(name) {
    this._name = name;
    this._behavior = 0;
  }
  get name() {   //getter
    return this._name;
  }
  get behavior() {   //getter
    return this._behavior;
  }
  incrementBehavior() {   //method
    this._behavior++;
  }
}
```

In the example above, we add getter methods for `name` and `behavior`. Notice, we also prepended our property names with underscores (`_name` and `_behavior`), which indicate these properties should not be accessed directly. Under the getters, we add a method named `.incrementBehavior()`. When you call `.incrementBehavior()` on a Dog instance, it adds `1` to the `_behavior` property. Between each of our methods, we did _not_ include commas.

### Case 1 : Surgery Department Management

```javascript
class Surgeon {
  constructor(name, department) {
    this._name = name;
    this._department = department;
    this._remainingVacationDays = 20;
  }  
get name() {
  return this._name;
}
get department() {
  return this._department;
}
get remainingVacationDays() {
  return this._remainingVacationDays;
} 
takeVacationDays(daysOff){ //method to update annual leave      
  this._remainingVacationDays -= daysOff;
}
}
// Instances called
const surgeonRomero = new Surgeon('Francisco Romero', 'Cardiovascular');
const surgeonJackson = new Surgeon('Ruth Jackson', 'Orthopedics');

```

### Method Calls

The syntax for calling methods and getters on an instance is the same as calling them on an object — append the instance with a period, then the property or method name. For methods, you must also include opening and closing parentheses. instance.method()

```javascript
let nikko = new Dog('Nikko'); 
// Create dog named Nikko
nikko.incrementBehavior(); 
// Add 1 to nikko instance's behavior

console.log(surgeonRomero.name)
surgeonRomero.takeVacationDays(3) //calling the method     
console.log(surgeonRomero.remainingVacationDays)
```

## 4. Inheritance

New keywords: `extends` and `super`

![](https://2068334946-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fow1iM27u7disHeJiSlBC%2Fuploads%2FFvl6WDCX9gLLJTfBFMKO%2Fimage.png?alt=media\&token=52bdc0f6-5cda-4c61-84c2-3d74ad051ce9)

{% hint style="info" %}
MUST USE 'SUPER' before 'this'
{% endhint %}

```javascript
class Animal {
  constructor(name) {
    this._name = name;
    this._behavior = 0;
  }
  get name() {
    return this._name;
  }
  get behavior() {
    return this._behavior;
  }
  incrementBehavior() {
    this._behavior++;
  }
} 
// extension inheritance
class Cat extends Animal {    // makes methods of parent avail  
  constructor(name, usesLitter) {
    super(name);    // calls constructor of parent class     
    this._usesLitter = usesLitter;
  }
}
```

* The `extends` keyword makes the methods of the animal class available inside the cat class.
* The constructor, called when you create a new `Cat` object, accepts two arguments, `name` and `usesLitter`.
* The `super` keyword calls the constructor of the parent class. In this case, `super(name)` passes the name argument of the `Cat` class to the constructor of the `Animal` class. When the `Animal` constructor runs, it sets `this._name = name;` for new `Cat` instances.
* `_usesLitter` is a new property that is unique to the `Cat` class, so we set it in the `Cat` constructor.

```javascript
class HospitalEmployee {
  constructor(name) {
    this._name = name;
    this._remainingVacationDays = 20;
  }
  
  get name() {
    return this._name;
  }
  
  get remainingVacationDays() {
    return this._remainingVacationDays;
  }
  
  takeVacationDays(daysOff) {
    this._remainingVacationDays -= daysOff;
  }
}

class Nurse extends HospitalEmployee {
 constructor(name, certifications) {
   super(name);
   this._certifications = certifications;
 } 
}

const nurseOlynyk = new Nurse('Olynyk', ['Trauma','Pediatrics']);

nurseOlynyk.takeVacationDays(5);
console.log(nurseOlynyk.remainingVacationDays)
```

### Case 2 : Surgery Department & Getter

```javascript
class HospitalEmployee {
  constructor(name) {
    this._name = name;
    this._remainingVacationDays = 20;
  }
  get name() {
    return this._name;
  }
  get remainingVacationDays() {
    return this._remainingVacationDays;
  } //method to update leave
  takeVacationDays(daysOff) {
    this._remainingVacationDays -= daysOff;
  }
}
// child class Nurse
class Nurse extends HospitalEmployee {
  constructor(name, certifications) {
    super(name);
    this._certifications = certifications;
  } 
  get certifications (){
    return this._certifications;
  } //method to update certs
  addCertification(newCertification){
    this._certifications.push(newCertification);
  } //push new cert to append to cert array
}
// call methods
const nurseOlynyk = new Nurse('Olynyk', ['Trauma','Pediatrics']);
nurseOlynyk.takeVacationDays(5);
console.log(nurseOlynyk.remainingVacationDays);
nurseOlynyk.addCertification('Genetics');
console.log(nurseOlynyk.certifications)
```

## 5. Static Methods

Sometimes you will want a class to have methods that aren’t available in individual instances, but that you can call directly from the class. Because of the `static` keyword, we can only access `.generateName()` by appending it to the `Animal` class.

The **`static`** keyword defines a static method or property for a class, or a [class static initialization block](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Class_static_initialization_blocks) (see the link for more information about this usage). Neither static methods nor static properties can be called on instances of the class. Instead, they're called on the class itself.

Static methods are often utility functions, such as functions to create or clone objects, whereas static properties are useful for caches, fixed-configuration, or any other data you don't need to be replicated across instances.

We call the `.generateName()` method with the following syntax:

```javascript
class Animal {
  constructor(name) {
    this._name = name;
    this._behavior = 0;
  }
 
  static generateName() {
    const names = ['Angel', 'Spike', 'Buffy', 'Willow', 'Tara'];
    const randomNumber = Math.floor(Math.random()*5);
    return names[randomNumber];
  }
} 

console.log(Animal.generateName()); // returns a name
const tyson = new Animal('Tyson'); 
tyson.generateName(); // TypeError
```

You cannot access the `.generateName()` method from instances of the `Animal` class or instances of its subclasses. The example above will result in an error, because you cannot call static methods (`.generateName()`) on an instance (`tyson`).

### Case 3 : Password Generator for Hospital Employees using Static Method

```javascript
class HospitalEmployee {
  constructor(name) {
    this._name = name;
    this._remainingVacationDays = 20;
  }
  
  get name() {
    return this._name;
  }
  
  get remainingVacationDays() {
    return this._remainingVacationDays;
  }
  
  takeVacationDays(daysOff) {
    this._remainingVacationDays -= daysOff;
  }
  static generatePassword() {
    const randomNumber = Math.floor(Math.random()*10000);
    return randomNumber;
  }
}

class Nurse extends HospitalEmployee {
  constructor(name, certifications) {
    super(name);
    this._certifications = certifications;
  } 
  
  get certifications() {
    return this._certifications;
  }
  
  addCertification(newCertification) {
    this.certifications.push(newCertification);
  }
}

const nurseOlynyk = new Nurse('Olynyk', ['Trauma','Pediatrics']);
nurseOlynyk.takeVacationDays(5);
console.log(nurseOlynyk.remainingVacationDays);
nurseOlynyk.addCertification('Genetics');
console.log(nurseOlynyk.certifications);class HospitalEmployee {
  constructor(name) {
    this._name = name;
    this._remainingVacationDays = 20;
  }
  
  get name() {
    return this._name;
  }
  
  get remainingVacationDays() {
    return this._remainingVacationDays;
  }
  
  takeVacationDays(daysOff) {
    this._remainingVacationDays -= daysOff;
  }
  static generatePassword() {
    const randomNumber = Math.floor(Math.random()*10000);
    return randomNumber;
  }
}

class Nurse extends HospitalEmployee {
  constructor(name, certifications) {
    super(name);
    this._certifications = certifications;
  } 
  
  get certifications() {
    return this._certifications;
  }
  
  addCertification(newCertification) {
    this.certifications.push(newCertification);
  }
}

const nurseOlynyk = new Nurse('Olynyk', ['Trauma','Pediatrics']);
nurseOlynyk.takeVacationDays(5);
console.log(nurseOlynyk.remainingVacationDays);
nurseOlynyk.addCertification('Genetics');
console.log(nurseOlynyk.certifications);
```

## 6. Modules

### &#x20;**# Intro to Runtime Environment**

A _runtime environment_ is where your program will be executed. JavaScript code may be executed in one of two runtime environments:

1. a browser’s runtime environment
2. the Node runtime environment

In each of these environments, different data values and functions are available, and these differences help distinguish front-end applications from back-end applications.

* Front-end JavaScript applications are executed in a browser’s runtime environment and have access to the `window` object.
* Back-end JavaScript applications are executed in the Node runtime environment and have access to the file system, databases, and networks attached to the server.

_Modules_ are reusable pieces of code in a file that can be exported and then imported for use in another file. A _modular_ program is one whose components can be separated, used individually, and recombined to create a complex system.

### &#x20; **# Module Implementation**

In JavaScript, there are two runtime environments and each has a preferred module implementation:

1. The [Node](https://nodejs.org/en/about/) runtime environment and the `module.exports` and `require()` syntax.
2. The browser’s runtime environment and the [ES6](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) `import`/`export` syntax.

{% hint style="info" %}
How to take input arguments

* process.argv\[x] takes the indexed input of the cmd interface
* `celsiusInput` is assigned `process.argv[2]`. When a program is executed in the Node environment, `process.argv` is an array holding the arguments provided. In this case, it looks like `['node', 'celsius-to-fahrenheit.js', '100']`. So, `process.argv[2]` returns `100`.
{% endhint %}

```javascript
/* $ node celsius-to-fahrenheit.js 100 // THIS IS THE CMD INPUT
100 degrees Celsius = 212 degrees Fahrenheit

celsius-to-fahrenheit.js */
function celsiusToFahrenheit(celsius) {
    return celsius * (9/5) + 32;
}
 
const celsiusInput = process.argv[2]; // Get the 3rd input from the argument list
const fahrenheitValue = celsiusToFahrenheit(celsiusInput);
 
console.log(`${celsiusInput} degrees Celsius = ${fahrenheitValue} degrees Fahrenheit`);
```

### &#x20; # Module.exports

To create a module, you simply have to create a new file where the functions can be declared. Then, to make these functions available to other files, add them as properties to the built-in `module.exports` object

```javascript
// 1. Define your function to export
function celsiusToFahrenheit(celsius) {
  return celsius * (9/5) + 32;
}
// 2. First method of exporting function - call "module.exports.function_name"
module.exports.celsiusToFahrenheit = celsiusToFahrenheit;
 // 3. Second method is to define function as module.exports.variable_name
module.exports.fahrenheitToCelsius = function(fahrenheit) {
  return (fahrenheit - 32) * (5/9);
};
```

### &#x20;# Require

The `require()` function accepts a string as an argument. That string provides the [file path](https://www.freecodecamp.org/news/requiring-modules-in-node-js-everything-you-need-to-know-e7fbd119be8/) to the module you would like to import. When you use `require()`, the entire `module.exports` object is returned and stored in the variable `converters`. This means that both the `.celsiusToFahrenheit()` and `.fahrenheitToCelsius()` methods can be used in this program!

```javascript
/* water-limits.js */
const converters = require('./converters.js'); // import module, ./ represents converters.js is in the same directory      
 
const freezingPointC = 0;
const boilingPointC = 100;
 
const freezingPointF = converters.celsiusToFahrenheit(freezingPointC);
const boilingPointF = converters.celsiusToFahrenheit(boilingPointC);
 
console.log(`The freezing point of water in Fahrenheit is ${freezingPointF}`);
console.log(`The boiling point of water in Fahrenheit is ${boilingPointF}`);

//TO IMPORT ONLY A SPECIFIC PART OF THE OBJECT (reduce importing uneccassary modules)
const { celsiusToFahrenheit } = require('./converters.js');
 
const celsiusInput = process.argv[2]; 
const fahrenheitValue = celsiusToFahrenheit(input);
 
console.log(`${celsiusInput} degrees Celsius = ${fahrenheitValue} degrees Fahrenheit`);
```

### Coding Challenges - Modules Concept Review 1

```javascript
// shape-area.js
const PI = Math.PI;
// 1. Define and export circleArea() and squareArea() below
function circleArea(radiusLength){
  return PI*radiusLength**2;
}
function squareArea(sideLength){
  return sideLength**2;
}
module.exports.circleArea = circleArea
module.exports.squareArea = squareArea

// 2. IMPORT MODULE from above shape-area.js
/* app.js */ 

const radius = 5;
const sideLength = 10;

// Option 1: import the entire shape-area.js module here.
const areaFunctions = require('./shape-area.js');

// Option 2: import circleArea and squareArea with object destructuring
const { circleArea, squareArea } = require("./shape-area.js")

// use the imported .circleArea() and .squareArea() methods here
const areaOfCircle = circleArea(radius);
const areaOfSquare = squareArea(sideLength);

console.log(areaOfCircle);
console.log(areaOfSquare);
```

## 7. Review of Modules - Import & Export for ES6

Exporting in 2 ways:

```javascript
// METHOD 1 : LIST ALL TOGETHER
export { toggleHiddenElement, changeToFunkyColor };

//METHOD 2 : CALL EXPORT BEFORE DECLARING VARIABLE
export const toggleHiddenElement = (domElement) => {
}
```

Importing:

```javascript
import { exportedResourceA, exportedResourceB } from '/path/to/module.js';
import { toggleHiddenElement, changeToFunkyColor } from '../modules/dom-functions.js'; 
      
```

Remember to adjust HTML for importing module:

```html
<script type="module" src="./secret-messages.js"> </script>  
have to add the above into the body  
```

ES6 Modules Code Challenge #2:

```javascript
import {changeText, changeToFunkyColor} from './module.js';
const header = document.getElementById("header");

// call changeText here
changeText(header, 'I did it!')
setInterval(()=> {
changeToFunkyColor(header)
}, 200);
```

#### Renaming exports from diff modules to avoid naming collisions

![import { module part as renamed\_name} from module file](https://2068334946-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fow1iM27u7disHeJiSlBC%2Fuploads%2FVGiI8DpsMKYcp6fCbLFN%2Fimage.png?alt=media\&token=f34f5733-24e9-4d90-aad4-8507c3538725)

### Project : Workaround Explorer (To do)

29 Nov 2021 5pm
