Categories
JavaScript Answers

How to Scroll to the Bottom of a Div with JavaScript?

Sometimes, we want to add a feature to let users scroll to the bottom of a div.

In this article, we’ll look at how to scroll to the bottom of a div with JavaScript.

Set scrollTop to scrollHeight

One way to scroll to the bottom of a div with JavaScript is to set the scrollTop property of the div object to the scrollHeight property of the same object.

For instance, we can write the following HTML:

<button>
  scroll to bottom
</button>
<div style='height: 300px; overflow-y: scroll'>
</div>

Then we can write the following JavaScript code:

const div = document.querySelector('div')
const button = document.querySelector('button')
for (let i = 1; i <= 100; i++) {
  const p = document.createElement('p')
  p.textContent = 'hello'
  div.appendChild(p)
}
button.addEventListener('click', () => {
  div.scrollTop = div.scrollHeight;
})

We set the height of the div to 300px and overflow-y to scroll to make the div scrollable.

Then in the JavaScript code, we get the div and the button.

Before we add the scrolling code, we add a for loop to create a p element and call appendChild to append the p elements to the div.

Next, we call addEventListener to add a click event listener to the button.

Then we set scrollTop to scrollHeight as we did in the callback to scroll to the bottom of the div.

scrollHeight is the height of an element’s content, including content that’s not visible on the screen due to overflow.

Set scrollTop to scrollHeight — clientHeight

clientHeight is CSS height + CSS padding + height of the horizontal scrollbar if prssent.

We can subtract that from scrollHeight and scroll to the bottom.

So we can write:

const div = document.querySelector('div')
const button = document.querySelector('button')
for (let i = 1; i <= 100; i++) {
  const p = document.createElement('p')
  p.textContent = 'hello'
  div.appendChild(p)
}
button.addEventListener('click', () => {
  div.scrollTop = div.scrollHeight - div.clientHeight;
})

and keep the HTML the same.

We still scroll down to the content height by setting scrollTop to the height of the content.

We just omit the scrollbar height.

The scrollIntoView Method

Another way to scroll to the bottom of a div is to select the element at the bottom of the div.

Then we can call scrollIntoView on it to scroll to the bottom.

For instance, we can write:

const div = document.querySelector('div')
const button = document.querySelector('button')
for (let i = 1; i <= 100; i++) {
  const p = document.createElement('p')
  p.textContent = 'hello'
  p.id = `el-${i}`
  div.appendChild(p)
}
button.addEventListener('click', () => {
  const bottomEl = document.querySelector('#el-100')
  bottomEl.scrollIntoView({
    behavior: 'smooth',
    block: 'end'
  });
})

to call scrollIntoView on the bottomEl , which is the element with ID el-100 .

This element is at the bottom of the div, so we’ll scroll to the bottom.

behavior set the scroll behavior.

'smooth' lets us do smooth scrolling.

block is set to 'end' to scroll to the end of the element.

Conclusion

There’re several ways we can use to scroll to the bottom of a div with JavaScript.

Categories
JavaScript

Declare and Use Variables in JavaScript

Variables are fundamental building blocks of a JavaScript program. They are names that represent entities that are stored in memory. In JavaScript, variables can contain almost anything. They can contain numbers, objects, booleans, strings, functions, arrays and varieties and combinations of those things. When you declare a variable, it’s a name that references those things in memory. In other words, you get the entity and manipulate it with variables.

They allow you to combine data and change them with various operators. We can add, subtract, multiply and divide numbers. Also, we can concatenate strings, call functions, and together they can combine useful results for programmers and users.

Variables have data types in JavaScript. Numbers, objects, booleans and strings are all types. There’s also the undefined type which is unique in JavaScript which represents variables that has no value set to them or haven’t been defined yet. Because variables can have different types, they can be converted into different data types when necessary. For example, strings that has numerical content only can be converted into a number and vice versa. We can do that with various operators and functions, like + to convert strings to numbers and toString function convert number to strings.

Declaring Variables

Declaring variables is the most basic part of a JavaScript program. We need to declare any variables that we’re going to use by using various keywords. In modern JavaScript, we should use the let keyword to declare variables. We use let because it allows us to declare block scoped variables, which means that if it’s declared in a function or class, then it cannot be changed outside of there. To use let to declare a variable, we put:

let a = 1;

The equal sign is the assignment operator. The value on the right side is set to the variable on the left side. So in this case, the variable a has the value 1. This declares primitive types, but we can also assign an object to a variable, like so:

let a = { num: 1 };

You might also have heard of the var keyword, but we shouldn’t use it since the scope isn’t constant. Any function or other code that’s in the declared in the same level as the variable declared with var or anything nested inside those entities can modify a variable declared with var , which can’t happen to variables declared with let .

We can also assign one variable to another variable. For instance, we can write:

let a = 1
let b = a // 1

The above example has a set to 1 then b is assigned to a which is assigned the value 1. b will get the value of 1 from a .

const is a keyword that we declare constants with. JavaScript constants cannot be assigned a new value after it’s set. However, we can modify the values inside an object set with const . So we cannot write:

const a = { num: 1};
a = 2; // error

but, we can write:

const a = { num: 1 };
a.num = 2;

The value of a will then be {num: 2} . const is very useful since it’s very hard to accidentally overwrite the value of your object in this case.

We can also declare global variables if 'use strict' is not added on top of our code. We declare global variables by writing a = 1; , which declares a in the global scope. This means that code written anywhere can modify it. This is the same as attaching a to the window object, so a = 1 is the same as window.a = 1 . It’s too easy to accidentally overwrite the value of a , so we should never declare global variables, unless you’re using something in the window object.

Variable Naming

Variables can be named in many ways. There are a few rules which apply when naming variables. They can start with upper or lower case letter, an underscore or a dollar sign. Variables in JavaScript are case sensitive, so a is not the same as A . Also, they cannot contain spaces. You can name variables as long as you want, but making them too long will probably confuse other developers reading your code.

Variable names can’t be the same reserved keywords which are listed below:

  • abstract
  • else
  • instanceof
  • switch
  • boolean
  • enum
  • int
  • synchronized
  • break
  • export
  • interface
  • this
  • byte
  • extends
  • long
  • throw
  • case
  • false
  • native
  • throws
  • catch
  • final
  • new
  • transient
  • char
  • finally
  • null
  • true
  • class
  • float
  • package
  • try
  • const
  • for
  • private
  • typeof
  • continue
  • function
  • protected
  • var
  • debugger
  • goto
  • public
  • void
  • default
  • if
  • return
  • volatile
  • delete
  • implements
  • short
  • while
  • do
  • import
  • static
  • with
  • double
  • in
  • super

Data Types

JavaScript variables has data types, which classifies the kind of data stored by its content. They distinguish one kind of data from another. For example, words are not the same as numbers. However, JavaScript is a loosely typed language, which means that different kinds of data to one variable. This means that we have to be careful when we’re comparing variables and when we’re manipulating them to not be manipulating the wrong kinds of data. This also means that when data is being compared, they’re automatically converted to the same type if possible for comparison.

JavaScript has give basic types of data. It has the number, string, boolean, NaN, and undefined types.

Number Type

Variable of type number stores numbers as the name suggests. JavaScript numbers can range from 5e-324 (-5 followed by 324 zeroes) to 1.7976931348623157e+308 ( same as 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000).

You can declare number variables by writing:

let x = 1;

We can convert variables of any types to number. If you convert a number to a number you still get a number. The Number function allows us to convert variables of any type to a number is possible. If it can’t, the NaN is returned, which means that the variable being converted isn’t a number.

Strings with numerical content can be converted to a number.

Number(“1”) // returns number 1

Text strings that can’t be converted to numbers return the value NaN:

Number(“duck”) // returns NaN

The Boolean value true returns the number 1:

Number(true) // returns 1

The Boolean value false returns the number 0:

Number(false) // returns 0

Other falsy values, which means that if they’re converted into a boolean, they’ll return false , also converted to 0. Falsy values in JavaScript include empty string (“”), 0, false, null, and undefined .

For example,

Number(‘’) // returns 0
Number(null) // returns 0
Number(undefined) // returns 0
Number(0) // returns 0

The + sign in front of a variable does the same thing as the Number function, so we can write:

+“1” // returns number 1
+“duck” // returns NaN
+‘’ // returns 0
+null // returns 0
+undefined // returns 0
+0 // returns 0

When you’re manipulating variables that may contain numerical values, then convert them to numbers first before combining them. However, when you’re multiply or diving 2 variables where one or both is a string, then JavaScript will convert them both to numbers before doing the operations. So, for example:

'2'*'2' // 4
'2'/2 // 1

But of course this won’t work if one or both strings don’t have numerical only content. For example: 'duck'*'goose' will return NaN since both of them aren’t numbers.

String Type

String types are variables that can store any characters. It can store letters, numbers, punctuation, and special escape characters to represent certain characters. The escape characters include:

  • ’ — single quote
  • ” — double quote
  • — backslash
  • n — new line
  • r — carriage return
  • t — tab
  • b — backspace
  • f — form feed

We can declare strings by writing:

let a  = 'string';

Single quotes and double quotes are the same. However, if you want to use quotes inside a string then you either have to use a different kind from the ones you use to wrap the string or use the slash character to escape the quote so that the JavaScript interpreter knows that it’s quotation mark and not a string delimiter. For example, we write:

let a = ""This is a string in quotes"";

or

let a = "'This is a string in quotes'";

Template Strings

Modern JavaScript also have template strings, starting from the 2015 and on. This is very handy since we can put variables inside strings so that our strings can have variables without having to concatenate them. We declare template strings with the backtick character, which is the character on your keyboard below the Escape key. We put variables inside our string by writing ${a} , where a is a variable.

For example, we can write:

let a = 1;
let b = `There is ${a} chicken.` // There is 1 chicken.

Boolean Type

Boolean type variables can only contain the values true or false . We declare it like so:

let a = true;

Variables of other types can be converted to boolean type. Almost everything converts to true except the following:

  • NaN
  • undefined
  • 0 (numeric value zero)
  • -0
  • “” (empty string)
  • false

NaN Type

NaN stands for not a number. Anything that can’t be converted or evaluated to a number will be NaN . For example, we cannot take a square root of a negative number and we can’t convert a non-numerical string into a number.

Undefined Type

A variable with the undefined type means that the variable has no value set to them or haven’t been defined yet.

These are the basic ways that JavaScript variables are declared and used in JavaScript. With these operations we can do a lot more than just declaring and assigning variables as we learn more about other parts of JavaScript.

Categories
React

Add Google Maps to a React App

React is a simple library for create interactive front end web apps. Its feature set is basic. It provides you with a component based architecture for building web apps. Each component does a small thing in an app, and they can be nested in each other or put side by side. Because of this, adding third party libraries is easy. Google Maps is a popular map system that can be incorporated into a React app because developers have wrote a component for it.

In this story, we will build an address book app, which uses those libraries, plus React Bootstrap, which has great integration with those libraries above to create forms. To start we need to run Create React App to scaffold the app. We run npx create-react-app address-book to create the app project folder with the initial files. The app will have a home page to display the contacts and let us open a modal to add a contact.

There will be a table that displays all the contacts and Edit and Delete buttons on each row to edit or delete each contact. The contacts will store in a central Redux store to store the contacts in a central place, making them easy to access. React Router will be used for routing.

Contacts will be saved in the back end spawned using the JSON server package, located at https://github.com/typicode/json-server. It will have a Google map for displaying the contact’s location. When the user clicks the Map button in each row, the user will see the map in a modal.

For form validation, then you need to use a third-party library. Formik and Yup, located at https://github.com/jaredpalmer/formik and https://github.com/jquense/yup work great together to allow us to take care of most form validation needs. Formik let us build the forms and display the errors, and handle form value changes, which is another thing we have to do all my hand otherwise. Yup let us write a schema for validating our form fields. It can check almost anything, with common validation code like email and required fields available as built-in functions. It can also check for fields that depend on other fields, like the postal code format depending on the country. Bootstrap forms can be used seamlessly with Formik and Yup.

We use react-google-maps to incorporate Google Maps into our React app, located at https://www.npmjs.com/package/react-google-maps. It is popular and it is actively maintained so it will work with the latest version of React. It is also easy to use.

Once that is done, we have to install some libraries. To install the libraries we mentioned above, we run npm i axios bootstrap formik react-bootstrap react-redux react-router-dom yup react-google-maps . Axios is the HTTP client that we use for making HTTP requests to back end. react-router-dom is the package name for the latest version of React Router.

Now that we have all the libraries installed, we can start building the app. All files will be in the src folder except mentioned otherwise. First we work on the Redux store. We create a file called actionCreator.js in the src folder and add the following:

import { SET_CONTACTS } from './actions';

const setContacts = (contacts) => {  
    return {  
        type: SET_CONTACTS,  
        payload: contacts  
    }  
};

export { setContacts };

This is the action creator for creating the action for storing the contacts in the store.

We create another file called actions.js and add:

const SET_CONTACTS = 'SET_CONTACTS';
export { SET_CONTACTS };

This just have the type constant for dispatching the action.

In App.js , we replace what is existing with the following:

import React from 'react';  
import { Router, Route, Link } from "react-router-dom";  
import HomePage from './HomePage';  
import { createBrowserHistory as createHistory } from 'history'  
import Navbar from 'react-bootstrap/Navbar';  
import Nav from 'react-bootstrap/Nav';  
import './App.css';  
const history = createHistory();

function App() {  
  return (  
    <div className="App">  
      <Router history={history}>  
        <Navbar bg="primary" expand="lg" variant="dark" >  
          <Navbar.Brand href="#home">Address Book App</Navbar.Brand>  
          <Navbar.Toggle aria-controls="basic-navbar-nav" />  
          <Navbar.Collapse id="basic-navbar-nav">  
            <Nav className="mr-auto">  
              <Nav.Link href="/">Home</Nav.Link>  
            </Nav>  
          </Navbar.Collapse>  
        </Navbar>  
        <Route path="/" exact component={HomePage} />  
      </Router>  
    </div>  
  );  
}

export default App;

This is where we add the navigation bar and show our routes routed by the React Router. In App.css, we replace the existing code with:

.App {  
  text-align: center;  
}

to center some text.

Next we build our contact form. This is the most logic heavy part of our app. We create a file called ContactForm.js and add:

import React from 'react';  
import { Formik } from 'formik';  
import Form from 'react-bootstrap/Form';  
import Col from 'react-bootstrap/Col';  
import InputGroup from 'react-bootstrap/InputGroup';  
import Button from 'react-bootstrap/Button';  
import * as yup from 'yup';  
import { COUNTRIES } from './exports';  
import PropTypes from 'prop-types';  
import { addContact, editContact, getContacts } from './requests';  
import { connect } from 'react-redux';  
import { setContacts } from './actionCreators';

const schema = yup.object({  
  firstName: yup.string().required('First name is required'),  
  lastName: yup.string().required('Last name is required'),  
  address: yup.string().required('Address is required'),  
  city: yup.string().required('City is required'),  
  region: yup.string().required('Region is required'),  
  country: yup.string().required('Country is required').default('Afghanistan'),  
  postalCode: yup  
    .string()  
    .when('country', {  
      is: 'United States',  
      then: yup.string().matches(/^[0-9]{5}(?:-[0-9]{4})?$/, 'Invalid postal code'),  
    })  
    .when('country', {  
      is: 'Canada',  
      then: yup.string().matches(/^[A-Za-z]\d[A-Za-z][ -\]?\d[A-Za-z]\d$/, 'Invalid postal code'),  
    })  
    .required(),  
  phone: yup  
    .string()  
    .when('country', {  
      is: country => ["United States", "Canada"].includes(country),  
      then: yup.string().matches(/^[2-9]\d{2}[2-9]\d{2}\d{4}$/, 'Invalid phone nunber')  
    })  
    .required(),  
  email: yup.string().email('Invalid email').required('Email is required'),  
  age: yup.number()  
    .required('Age is required')  
    .min(0, 'Minimum age is 0')  
    .max(200, 'Maximum age is 200'),  
});

function ContactForm({  
  edit,  
  onSave,  
  setContacts,  
  contact,  
  onCancelAdd,  
  onCancelEdit,  
}) {  
  const handleSubmit = async (evt) => {  
    const isValid = await schema.validate(evt);  
    if (!isValid) {  
      return;  
    }  
    if (!edit) {  
      await addContact(evt);  
    }  
    else {  
      await editContact(evt);  
    }  
    const response = await getContacts();  
    setContacts(response.data);  
    onSave();  
  }

  return (  
    <div className="form">  
      <Formik  
        validationSchema={schema}  
        onSubmit={handleSubmit}  
        initialValues={contact || {}}  
      >  
        {({  
          handleSubmit,  
          handleChange,  
          handleBlur,  
          values,  
          touched,  
          isInvalid,  
          errors,  
        }) => (  
            <Form noValidate onSubmit={handleSubmit}>  
              <Form.Row>  
                <Form.Group as={Col} md="12" controlId="firstName">  
                  <Form.Label>First name</Form.Label>  
                  <Form.Control  
                    type="text"  
                    name="firstName"  
                    placeholder="First Name"  
                    value={values.firstName || ''}  
                    onChange={handleChange}  
                    isInvalid={touched.firstName && errors.firstName}  
                  />  
                  <Form.Control.Feedback type="invalid">  
                    {errors.firstName}  
                  </Form.Control.Feedback>  
                </Form.Group>  
                <Form.Group as={Col} md="12" controlId="lastName">  
                  <Form.Label>Last name</Form.Label>  
                  <Form.Control  
                    type="text"  
                    name="lastName"  
                    placeholder="Last Name"  
                    value={values.lastName || ''}  
                    onChange={handleChange}  
                    isInvalid={touched.firstName && errors.lastName}  
                  /><Form.Control.Feedback type="invalid">  
                    {errors.lastName}  
                  </Form.Control.Feedback>  
                </Form.Group>  
                <Form.Group as={Col} md="12" controlId="address">  
                  <Form.Label>Address</Form.Label>  
                  <InputGroup>  
                    <Form.Control  
                      type="text"  
                      placeholder="Address"  
                      aria-describedby="inputGroupPrepend"  
                      name="address"  
                      value={values.address || ''}  
                      onChange={handleChange}  
                      isInvalid={touched.address && errors.address}  
                    />  
                    <Form.Control.Feedback type="invalid">  
                      {errors.address}  
                    </Form.Control.Feedback>  
                  </InputGroup>  
                </Form.Group>  
              </Form.Row>  
              <Form.Row>  
                <Form.Group as={Col} md="12" controlId="city">  
                  <Form.Label>City</Form.Label>  
                  <Form.Control  
                    type="text"  
                    placeholder="City"  
                    name="city"  
                    value={values.city || ''}  
                    onChange={handleChange}  
                    isInvalid={touched.city && errors.city}  
                  /><Form.Control.Feedback type="invalid">  
                    {errors.city}  
                  </Form.Control.Feedback>  
                </Form.Group>  
                <Form.Group as={Col} md="12" controlId="region">  
                  <Form.Label>Region</Form.Label>  
                  <Form.Control  
                    type="text"  
                    placeholder="Region"  
                    name="region"  
                    value={values.region || ''}  
                    onChange={handleChange}  
                    isInvalid={touched.region && errors.region}  
                  />  
                  <Form.Control.Feedback type="invalid">  
                    {errors.region}  
                  </Form.Control.Feedback>  
                </Form.Group><Form.Group as={Col} md="12" controlId="country">  
                  <Form.Label>Country</Form.Label>  
                  <Form.Control  
                    as="select"  
                    placeholder="Country"  
                    name="country"  
                    onChange={handleChange}  
                    value={values.country || ''}  
                    isInvalid={touched.region && errors.country}>  
                    {COUNTRIES.map(c => <option key={c} value={c}>{c}</option>)}  
                  </Form.Control>  
                  <Form.Control.Feedback type="invalid">  
                    {errors.country}  
                  </Form.Control.Feedback>  
                </Form.Group><Form.Group as={Col} md="12" controlId="postalCode">  
                  <Form.Label>Postal Code</Form.Label>  
                  <Form.Control  
                    type="text"  
                    placeholder="Postal Code"  
                    name="postalCode"  
                    value={values.postalCode || ''}  
                    onChange={handleChange}  
                    isInvalid={touched.postalCode && errors.postalCode}  
                  /><Form.Control.Feedback type="invalid">  
                    {errors.postalCode}  
                  </Form.Control.Feedback>  
                </Form.Group><Form.Group as={Col} md="12" controlId="phone">  
                  <Form.Label>Phone</Form.Label>  
                  <Form.Control  
                    type="text"  
                    placeholder="Phone"  
                    name="phone"  
                    value={values.phone || ''}  
                    onChange={handleChange}  
                    isInvalid={touched.phone && errors.phone}  
                  /><Form.Control.Feedback type="invalid">  
                    {errors.phone}  
                  </Form.Control.Feedback>  
                </Form.Group><Form.Group as={Col} md="12" controlId="email">  
                  <Form.Label>Email</Form.Label>  
                  <Form.Control  
                    type="text"  
                    placeholder="Email"  
                    name="email"  
                    value={values.email || ''}  
                    onChange={handleChange}  
                    isInvalid={touched.email && errors.email}  
                  /><Form.Control.Feedback type="invalid">  
                    {errors.email}  
                  </Form.Control.Feedback>  
                </Form.Group><Form.Group as={Col} md="12" controlId="age">  
                  <Form.Label>Age</Form.Label>  
                  <Form.Control  
                    type="text"  
                    placeholder="Age"  
                    name="age"  
                    value={values.age || ''}  
                    onChange={handleChange}  
                    isInvalid={touched.age && errors.age}  
                  /><Form.Control.Feedback type="invalid">  
                    {errors.age}  
                  </Form.Control.Feedback>  
                </Form.Group>  
              </Form.Row>  
              <Button type="submit" style={{ 'marginRight': '10px' }}>Save</Button>  
              <Button type="button" onClick={edit ? onCancelEdit : onCancelAdd}>Cancel</Button>  
            </Form>  
          )}  
      </Formik>  
    </div>  
  );  
}

ContactForm.propTypes = {  
  edit: PropTypes.bool,  
  onSave: PropTypes.func,  
  onCancelAdd: PropTypes.func,  
  onCancelEdit: PropTypes.func,  
  contact: PropTypes.object  
}

const mapStateToProps = state => {  
  return {  
    contacts: state.contacts,  
  }  
}

const mapDispatchToProps = dispatch => ({  
  setContacts: contacts => dispatch(setContacts(contacts))  
})

export default connect(  
  mapStateToProps,  
  mapDispatchToProps  
)(ContactForm);

We use Formik to facilitate building our contact form here, with our Boostrap Form component nested in the Formik component so that we can use Formik’s handleChange, handleSubmit, values, touched and errors parameters. handleChange is a function that let us update the form field data from the inputs without writing the code ourselves. handleSubmit is the function that we passed into the onSubmit handler of the Formik component. The parameter in the function is the data we entered, with the field name as the key, as defined by the name attribute of each field and the value of each field as the value of those keys. Notice that in each value prop, we have ||'' so we do not get undefined values and prevent uncontrolled form warnings from getting triggered.

To display form validation messages, we have to pass in the isInvalid prop to each Form.Control component. The schema object is what Formik will check against for form validation. The argument in the required function is the validation error message. The second argument of the matches, min and max functions are also validation messages.

The parameter of the ContactForm function are props, which we will pass in from the HomePage component that we will build later. The handleSubmit function checks if the data is valid, then if it is then it will proceed to save according to whether it is adding or editing a contact. Then when saving is successful we set the contacts in the store and call onSave prop, which is a function to close the modal the form is in. The modal will be defined in the home page.

mapStateToProps is a function provided by React Redux so that we can map the state directly to the props of our component as the function name suggests. mapDispatchToProps allows us to call the function in the props of the component called setContacts to dispatch the action as we defined in actionCreators.js

Next we create a file called exports.js , and put:

export const COUNTRIES = ["Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Anguilla", "Antigua &amp; Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas"  
    , "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia &amp; Herzegovina", "Botswana", "Brazil", "British Virgin Islands"  
    , "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Chad", "Chile", "China", "Colombia", "Congo", "Cook Islands", "Costa Rica"  
    , "Cote D Ivoire", "Croatia", "Cruise Ship", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea"  
    , "Estonia", "Ethiopia", "Falkland Islands", "Faroe Islands", "Fiji", "Finland", "France", "French Polynesia", "French West Indies", "Gabon", "Gambia", "Georgia", "Germany", "Ghana"  
    , "Gibraltar", "Greece", "Greenland", "Grenada", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea Bissau", "Guyana", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India"  
    , "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kuwait", "Kyrgyz Republic", "Laos", "Latvia"  
    , "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Mauritania"  
    , "Mauritius", "Mexico", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Namibia", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia"  
    , "New Zealand", "Nicaragua", "Niger", "Nigeria", "Norway", "Oman", "Pakistan", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal"  
    , "Puerto Rico", "Qatar", "Reunion", "Romania", "Russia", "Rwanda", "Saint Pierre &amp; Miquelon", "Samoa", "San Marino", "Satellite", "Saudi Arabia", "Senegal", "Serbia", "Seychelles"  
    , "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "South Africa", "South Korea", "Spain", "Sri Lanka", "St Kitts &amp; Nevis", "St Lucia", "St Vincent", "St. Lucia", "Sudan"  
    , "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor L'Este", "Togo", "Tonga", "Trinidad &amp; Tobago", "Tunisia"  
    , "Turkey", "Turkmenistan", "Turks &amp; Caicos", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "United States Minor Outlying Islands", "Uruguay"  
    , "Uzbekistan", "Venezuela", "Vietnam", "Virgin Islands (US)", "Yemen", "Zambia", "Zimbabwe"];

These are countries for the countries field in the form.

Next, we make the Google Map component for displaying the contact’s location. We make a file called MapComponent.js and add:

import React from "react";  
import { compose, withProps } from "recompose";  
import { GOOGLE_API_KEY } from "./requests";import {  
  withGoogleMap,  
  GoogleMap,  
  Marker,  
  withScriptjs,  
} from "react-google-maps";const MapComponent = compose(  
  withProps({  
    googleMapURL: `[https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&v=3.exp&libraries=geometry,drawing,places`](https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&v=3.exp&libraries=geometry,drawing,places`),  
    loadingElement: <div style={{ height: `100%` }} />,  
    containerElement: <div style={{ height: `400px` }} />,  
    mapElement: <div style={{ height: `100%` }} />,  
  }),  
  withScriptjs,  
  withGoogleMap  
)(({ lat, lng }) => (  
  <GoogleMap defaultZoom={8} defaultCenter={{ lat, lng }}>  
    <Marker position={{ lat, lng }} />  
  </GoogleMap>  
));

export default MapComponent;

The fields in the argument of the withProps function call are props required for the GoogleMap component, withScriptjs loads the Google Map JavaScript files from Google. lat and lng are props that we pass into the component to display the location by latitude and longitude respectively.

In HomePage.js , we put:

import React from "react";  
import { useState, useEffect } from "react";  
import Table from "react-bootstrap/Table";  
import ButtonToolbar from "react-bootstrap/ButtonToolbar";  
import Button from "react-bootstrap/Button";  
import Modal from "react-bootstrap/Modal";  
import ContactForm from "./ContactForm";  
import "./HomePage.css";  
import MapComponent from "./MapComponent";  
import { connect } from "react-redux";  
import { getContacts, deleteContact, getLatLng } from "./requests";function HomePage() {  
  const [openAddModal, setOpenAddModal] = useState(false);  
  const [openEditModal, setOpenEditModal] = useState(false);  
  const [openMapModal, setOpenMapModal] = useState(false);  
  const [initialized, setInitialized] = useState(false);  
  const [loc, setLoc] = useState({  
    lat: 0,  
    lng: 0,  
  });  
  const [selectedContact, setSelectedContact] = useState({});  
  const [contacts, setContacts] = useState([]); 
  const openModal = () => {  
    setOpenAddModal(true);  
  }; 

  const closeModal = () => {  
    setOpenAddModal(false);  
    setOpenEditModal(false);  
    setOpenMapModal(false);  
    getData();  
  }; 

  const cancelAddModal = () => {  
    setOpenAddModal(false);  
  }; 

  const editContact = contact => {  
    setSelectedContact(contact);  
    setOpenEditModal(true);  
  }; 

  const cancelEditModal = () => {  
    setOpenEditModal(false);  
  }; 

  const getData = async () => {  
    const response = await getContacts();  
    setContacts(response.data);  
    setInitialized(true);  
  }; 

  const deleteSelectedContact = async id => {  
    await deleteContact(id);  
    getData();  
  }; 

  const openMap = async contact => {  
    try {  
      const address = `${contact.addressLineOne}, ${contact.addressLineTwo}, ${contact.city}, ${contact.country}`;  
      const response = await getLatLng(address);  
      const loc = response.data.results[0].geometry.location;  
      setLoc(loc);  
      setOpenMapModal(true);  
    } catch (ex) {  
      console.log(ex);  
    }  
  }; 

  useEffect(() => {  
    if (!initialized) {  
      getData();  
    }  
  }); 

  return (  
    <div className="home-page">  
      <h1>Contacts</h1>  
      <Modal show={openAddModal} onHide={closeModal}>  
        <Modal.Header closeButton>  
          <Modal.Title>Add Contact</Modal.Title>  
        </Modal.Header>  
        <Modal.Body>  
          <ContactForm  
            edit={false}  
            onSave={closeModal.bind(this)}  
            onCancelAdd={cancelAddModal}  
          />  
        </Modal.Body>  
      </Modal> <Modal show={openEditModal} onHide={closeModal}>  
        <Modal.Header closeButton>  
          <Modal.Title>Edit Contact</Modal.Title>  
        </Modal.Header>  
        <Modal.Body>  
          <ContactForm  
            edit={true}  
            onSave={closeModal.bind(this)}  
            contact={selectedContact}  
            onCancelEdit={cancelEditModal}  
          />  
        </Modal.Body>  
      </Modal> <Modal show={openMapModal} onHide={closeModal}>  
        <Modal.Header closeButton>  
          <Modal.Title>Map</Modal.Title>  
        </Modal.Header>  
        <Modal.Body>  
          <MapComponent  
            lat={loc.lat}  
            lng={loc.lng}  
          />  
        </Modal.Body>  
      </Modal> <ButtonToolbar onClick={openModal}>  
        <Button variant="outline-primary">Add Contact</Button>  
      </ButtonToolbar>  
      <br />  
      <Table striped bordered hover>  
        <thead>  
          <tr>  
            <th>First Name</th>  
            <th>Last Name</th>  
            <th>Address</th>  
            <th>City</th>  
            <th>Country</th>  
            <th>Postal Code</th>  
            <th>Phone</th>  
            <th>Email</th>  
            <th>Age</th>  
            <th>Map</th>  
            <th>Edit</th>  
            <th>Delete</th>  
          </tr>  
        </thead>  
        <tbody>  
          {contacts.map(c => (  
            <tr key={c.id}>  
              <td>{c.firstName}</td>  
              <td>{c.lastName}</td>  
              <td>{c.address}</td>  
              <td>{c.city}</td>  
              <td>{c.country}</td>  
              <td>{c.postalCode}</td>  
              <td>{c.phone}</td>  
              <td>{c.email}</td>  
              <td>{c.age}</td>  
              <td>  
                <Button  
                  variant="outline-primary"  
                  onClick={openMap.bind(this, c)}  
                >  
                  Map  
                </Button>  
              </td>  
              <td>  
                <Button  
                  variant="outline-primary"  
                  onClick={editContact.bind(this, c)}  
                >  
                  Edit  
                </Button>  
              </td>  
              <td>  
                <Button  
                  variant="outline-primary"  
                  onClick={deleteSelectedContact.bind(this, c.id)}  
                >  
                  Delete  
                </Button>  
              </td>  
            </tr>  
          ))}  
        </tbody>  
      </Table>  
    </div>  
  );  
}

const mapStateToProps = state => {  
  return {  
    contacts: state.contacts,  
  };  
};

export default connect(  
  mapStateToProps,  
  null  
)(HomePage);

It has the table to display the contacts and buttons to add, edit, and delete contact. It gets data once on the first load with the getData function call in the useEffect‘s callback function. useEffect‘s callback is called on every renders so we want to set a initialized flag and check that it loads only if it’s true.

Note that we pass in all the props in this component to the ContactForm component. To pass an argument a onClick handler function, we have to call bind on the function and pass in the argument for the function as a second argument to bind . For example, in this file, we have editContact.bind(this, c) , where c is the contact object. The editContact function is defined as follows:

const editContact = (contact) => {  
  setSelectedContact(contact);  
  setOpenEditModal(true);  
}

c is the contact parameter we pass in.

Next we create a file called HomePage.css and put:

.home-page {  
  padding: 20px;  
}

to add some padding.

In index.js , we replace the existing code with:

import React from 'react';  
import ReactDOM from 'react-dom';  
import './index.css';  
import App from './App';  
import \* as serviceWorker from './serviceWorker';  
import { contactsReducer } from './reducers';  
import { Provider } from 'react-redux'  
import { createStore, combineReducers } from 'redux'const addressBookApp = combineReducers({  
    contacts: contactsReducer,  
})const store = createStore(addressBookApp)ReactDOM.render(  
    <Provider store={store}>  
        <App />  
    </Provider>  
    , document.getElementById('root'));// If you want your app to work offline and load faster, you can change  
// unregister() to register() below. Note this comes with some pitfalls.  
// Learn more about service workers: [https://bit.ly/CRA-PWA](https://bit.ly/CRA-PWA)  
serviceWorker.unregister();

We combined the reducers and create the store, then inject it to our app with the Provider component so that we can use it everywhere in the app.

Then we make a file called reducers.js , and add:

import { SET_CONTACTS } from './actions';

function contactsReducer(state = {}, action) {  
    switch (action.type) {  
        case SET_CONTACTS:  
            state = JSON.parse(JSON.stringify(action.payload));  
            return state;  
        default:  
            return state  
    }  
}

export { contactsReducer };

This is the reducer where we store the contacts that we dispatch by calling the prop provided by the mapDispatchToProps function in our components.

Then we make a file called requests.js , and add:

const APIURL = "http://localhost:3000";  
const MAPURL = "https://maps.googleapis.com/maps/api/geocode/json?address=";  
const axios = require("axios");  
export const GOOGLE_API_KEY = "your API key";

export const getContacts = () => axios.get(`${APIURL}/contacts`);

export const addContact = data => axios.post(`${APIURL}/contacts`, data);

export const editContact = data =>  
  axios.put(`${APIURL}/contacts/${data.id}`, data);

export const deleteContact = id => axios.delete(`${APIURL}/contacts/${id}`);

export const getLatLng = address => {  
  return axios.get(  
    `${MAPURL}${encodeURIComponent(address)}&key=${GOOGLE_API_KEY}`  
  );  
};

These are functions are making our HTTP requests to the back end to save and delete contacts.

Finally, in public/index.html , we replace the existing code with:

<!DOCTYPE html>  
<html lang="en">

<head>  
  <meta charset="utf-8" />  
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />  
  <meta name="viewport" content="width=device-width, initial-scale=1" />  
  <meta name="theme-color" content="#000000" />  
  <meta name="description" content="Web site created using create-react-app" />  
  <link rel="apple-touch-icon" href="logo192.png" />  
  <link rel="manifest" crossorigin="use-credentials" href="%PUBLIC_URL%/manifest.json" /><!--  
      manifest.json provides metadata used when your web app is installed on a  
      user's mobile device or desktop. See [https://developers.google.com/web/fundamentals/web-app-manifest/](https://developers.google.com/web/fundamentals/web-app-manifest/)  
    -->  
  <!--  
      Notice the use of %PUBLIC_URL% in the tags above.  
      It will be replaced with the URL of the `public` folder during the build.  
      Only files inside the `public` folder can be referenced from the HTML.Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL.  
      Learn how to configure a non-root public URL by running `npm run build`.  
    -->  
  <title>React Address Book App</title>  
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"  
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />  
</head>

<body>  
  <noscript>You need to enable JavaScript to run this app.</noscript>  
  <div id="root"></div>  
  <!--  
      This HTML file is a template.  
      If you open it directly in the browser, you will see an empty page. You can add webfonts, meta tags, or analytics to this file.  
      The build step will place the bundled scripts into the <body> tag.To begin the development, run `npm start` or `yarn start`.  
      To create a production bundle, use `npm run build` or `yarn build`.  
    -->  
</body>

</html>

to change the title and add the Bootstrap stylesheet.

Now we can run the app by running set PORT=3001 && react-scripts start on Windows or PORT=3006 react-scripts start on Linux.

To start back end, we first install the json-server package by running npm i json-server . Them go to our project folder and run:

json-server --watch db.json

In db.json , change the text to:

{  
  "contacts": [  
  ]  
}

so that we have the contacts endpoints defined in requests.js available.

Categories
JavaScript Answers

How to Scroll to the Bottom of a Div with JavaScript?

Sometimes, we want to add a feature to let users scroll to the bottom of a div.

In this article, we’ll look at how to scroll to the bottom of a div with JavaScript.

Set scrollTop to scrollHeight

One way to scroll to the bottom of a div with JavaScript is to set the scrollTop property of the div object to the scrollHeight property of the same object.

For instance, we can write the following HTML:

<button>
  scroll to bottom
</button>
<div style='height: 300px; overflow-y: scroll'>
</div>

Then we can write the following JavaScript code:

const div = document.querySelector('div')
const button = document.querySelector('button')
for (let i = 1; i <= 100; i++) {
  const p = document.createElement('p')
  p.textContent = 'hello'
  div.appendChild(p)
}
button.addEventListener('click', () => {
  div.scrollTop = div.scrollHeight;
})

We set the height of the div to 300px and overflow-y to scroll to make the div scrollable.

Then in the JavaScript code, we get the div and the button.

Before we add the scrolling code, we add a for loop to create a p element and call appendChild to append the p elements to the div.

Next, we call addEventListener to add a click event listener to the button.

Then we set scrollTop to scrollHeight as we did in the callback to scroll to the bottom of the div.

scrollHeight is the height of an element’s content, including content that’s not visible on the screen due to overflow.

Set scrollTop to scrollHeight — clientHeight

clientHeight is CSS height + CSS padding + height of the horizontal scrollbar if prssent.

We can subtract that from scrollHeight and scroll to the bottom.

So we can write:

const div = document.querySelector('div')
const button = document.querySelector('button')
for (let i = 1; i <= 100; i++) {
  const p = document.createElement('p')
  p.textContent = 'hello'
  div.appendChild(p)
}
button.addEventListener('click', () => {
  div.scrollTop = div.scrollHeight - div.clientHeight;
})

and keep the HTML the same.

We still scroll down to the content height by setting scrollTop to the height of the content.

We just omit the scrollbar height.

The scrollIntoView Method

Another way to scroll to the bottom of a div is to select the element at the bottom of the div.

Then we can call scrollIntoView on it to scroll to the bottom.

For instance, we can write:

const div = document.querySelector('div')
const button = document.querySelector('button')
for (let i = 1; i <= 100; i++) {
  const p = document.createElement('p')
  p.textContent = 'hello'
  p.id = `el-${i}`
  div.appendChild(p)
}
button.addEventListener('click', () => {
  const bottomEl = document.querySelector('#el-100')
  bottomEl.scrollIntoView({
    behavior: 'smooth',
    block: 'end'
  });
})

to call scrollIntoView on the bottomEl , which is the element with ID el-100 .

This element is at the bottom of the div, so we’ll scroll to the bottom.

behavior set the scroll behavior.

'smooth' lets us do smooth scrolling.

block is set to 'end' to scroll to the end of the element.

Conclusion

There’re several ways we can use to scroll to the bottom of a div with JavaScript.

Categories
JavaScript Answers

How to Restrict HTML Text Input to Only Allow Numbers?

Sometimes, we only want users to enter numbers into an HTML text input.

In this article, we’ll look at how to restrict HTML text input to only allow numbers.

Watching Keypresses

We can watch keypresses by checking for inputted values and pasted data.

To do this, we write the following HTML:

<input type="text" />

And we can write the following JavaScript:

const input = document.querySelector("input");
input.addEventListener("keypress", (evt) => {
  const theEvent = evt || window.event;
  let key = theEvent.keyCode || theEvent.which;
  key = String.fromCharCode(key);
  const regex = /[0-9]|./;
  if (!regex.test(key)) {
    theEvent.returnValue = false;
    if (theEvent.preventDefault) theEvent.preventDefault();
  }
});

We listen to the keypress event with an event listener.

In the keypress event callback, we get the keyCode property to get the code.

Then we get the value of key pressed with String.fromCharCode .

Then we call regex.test to check if the key value is a digit.

If it’s not, then we call preventDefault() to prevent the character from being added to the inputted value string.

Also, we can just remove all the non-digit characters from the inputted value string.

For instance, we can write:

const input = document.querySelector("input");
input.addEventListener("keyup", (evt) => {
  input.value = evt.target.value.replace(/[^d]/, "");
});

We get the inputted value with evt.target.value ,.

And we call replace to get all the non-digit characters with an empty string.

Then we assign the returned string to input.value to update the inputted value.

Set Input Type Attribute to number

Also, we can just set the type attribute of input to number .

To do this, we write:

<input type='number' />

Then we don’t need to add any JavaScript code to remove the non-digit characters from the inputted value string.

Conclusion

There’re several ways to restrict an input’s value to digits only.