Categories
Bootstrap HTML

Bootstrap 5 — Browser Support and Customization

Bootstrap 5 is in alpha when this is written and it’s subject to change.

Bootstrap is a popular UI library for any JavaScript apps.

In this article, we’ll look at how to customize and extend Bootstrap 5 features and browser and device support.

Supported Browsers

Bootstrap supports modern browsers including the latest version of Legacy Edge.

Alternative browsers that use the Webkit, Blink, or Gecko engines should also display properly.

Mobile Devices

Bootstrap support most modern Android devices.

Android browser and webview from Android 6 or later is also supporred.

Chrome and Firefox in Android are also supported

iOS Chrome, Firefox, and Safari are also supported.

Desktop Browsers

On Mac, Chrome, Firefox, Edge, Opera, and Safari are supported.

On Windows, Chrome, Firefox, Edge, and Opera are supported.

Internet Explorer

Internet Explorer is no longer supported in Bootstrap 5.

Modals and Dropdowns on Mobile

overflow: hidden on body support is limited in iOS and Android.

This means that we can still scroll even with the CSS applied to it in those platforms.

iOS Text Fields and Scrolling

Since iOS 9.2, while a modal is open, the initial touch of a scroll features with an input element or body underneath the modal will be scrolled instead of the modal itself.

Navbar Dropdowns

.dropdown-navbar element isn’t used in iOS in the nav because of complexity with z-index.

Browser Zooming

Zooming may show rendering artifacts on the page and there’s no clean solution for this at the moment.

Sass

We can import SASS modules from the Bootstrap package.

This way, we can extend the Bootstrap SASS code with our own code.

For example, we can write:

@import "../node_modules/bootstrap/scss/bootstrap";

to include all Bootstrap styles.

We can also import them pieces one-by-one by writing:

@import "../node_modules/bootstrap/scss/functions";  
@import "../node_modules/bootstrap/scss/variables";  
@import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/reboot";  
@import "../node_modules/bootstrap/scss/type";  
@import "../node_modules/bootstrap/scss/images";  
@import "../node_modules/bootstrap/scss/code";  
@import "../node_modules/bootstrap/scss/grid";

Then we can override the values with our own code. For example, we can write:

$body-bg: green;  
$body-color: orange;

Modify Map

We can modify the $theme-colors map with our own code by overriding the values of the variables.

For example, we can write:

$primary: green;  
$danger: red;

And then they’ll override the values in the map.

Add to Map

We can add new values to the $theme-colors map.

For example, we can write:

$theme-colors: (  
  "custom-color": brown  
);

to our $theme-colors map.

Remove from Map

We can remove a color from a map with the map-remove function.

For example, we can write:

@import "../node_modules/bootstrap/scss/functions";  
@import "../node_modules/bootstrap/scss/variables";  
@import "../node_modules/bootstrap/scss/mixins";
$theme-colors: map-remove($theme-colors, "info", "light");
@import "../node_modules/bootstrap/scss/root";  
@import "../node_modules/bootstrap/scss/reboot";  
@import "../node_modules/bootstrap/scss/type";

We remove the info and light values from the $theme-colors map.

Colors

Bootstrap 5 dropped the color() , theme-color() , and gray() functions since those values are available as standalone variables.

For example, instead of using the theme-color("primary") to get the primary color value, we use $primary .

CSS Variables

Bootstrap includes many CSS variables that we can use in our app.

They include:

:root {  
  --bs-blue: #0d6efd;  
  --bs-indigo: #6610f2;  
  --bs-purple: #6f42c1;  
  --bs-pink: #d63384;  
  --bs-red: #dc3545;  
  --bs-orange: #fd7e14;  
  --bs-yellow: #ffc107;  
  --bs-green: #28a745;  
  --bs-teal: #20c997;  
  --bs-cyan: #17a2b8;  
  --bs-white: #fff;  
  --bs-gray: #6c757d;  
  --bs-gray-dark: #343a40;  
  --bs-primary: #0d6efd;  
  --bs-secondary: #6c757d;  
  --bs-success: #28a745;  
  --bs-info: #17a2b8;  
  --bs-warning: #ffc107;  
  --bs-danger: #dc3545;  
  --bs-light: #f8f9fa;  
  --bs-dark: #343a40;  
  --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";  
  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;  
  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));  
}

We can use these values for colors and fonts.

Conclusion

Bootstrap 5 supports most modern browsers.

Also, we can customize values in various ways.

Categories
Bootstrap HTML JavaScript

Getting Started with Bootstrap 5

Bootstrap 5 is in alpha when this is written and it’s subject to change.

Bootstrap is a popular UI library for any JavaScript apps.

In this article, we’ll look at the new features of Bootstrap 5 and how to get started using it.

New Look and Feel

Bootstrap 5 gives us a new look and feel that’s different from version 4.5.0.

Internet Explorer support is removed so that the package is now smaller.

Font sizes are now responsive rather than fixed sized.

Card decks are removed.

Navbar now requires less markup to add.

It’s no longer display as an inline-block element by default.

There’s also a new custom SVG icon library included with many icons.

jQuery and JavaScript

jQuery is removed as a dependency for Bootstrap since JavaScrtipt can provide the same functionality without the extra dependency.

Now that we don’t need it, that’s one less dependency that we’ve to include in our app.

The button plugin is now CSS only.

CSS Custom Properties

Bootstrap now uses CSS custom properties for styling.

This allows Bootstrap to reuse values for styling.

Enhanced Grid System

The grid system has improved.

Bootstrap 5 has a new grid tier, which is the xxl tier.

This is now the smallest breakpoint.

.gutter classes have been replaced with the .g* utiulities for gutter spacing.

Form layout options are replaced with a new grid system.

Vertical spacing classes are added.

Columns are no longer position: relative by default.

To define new gutters, we can write:

<div class="row g-5">  
  <div class="col">...</div>  
  <div class="col">...</div>  
</div>

We have the g-5 class for defining a butter,

Quick Start

We can download the Bootstrap package by going to the downloads page at https://v5.getbootstrap.com/docs/5.0/getting-started/download/.

Also, we can include it with the CDN. The CSS can be added by writing:

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">

and the JavaScript can be added by writing:

<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>

Together, we can write:

<!doctype html>  
<html lang="en">  
  <head>  
    <meta charset="utf-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous"> <title>Hello, world!</title>  
  </head>  
  <body>  
    <h1>Hello</h1> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>  
  </body>  
</html>

to get started.

The meta tags are for making the content scale properly on any screens.

The link tag has the Bootstrap CSS.

And the script tags have the Bootstrap JavaScript.

The JavaScript is optional.

The order have to be the same as what’s listed since Bootstrap requires Popper.js.

Package Managers

Bootstrap is also available as a package in NPM.

To install it, we run:

npm install bootstrap@next

Then we can include by writing:

const bootstrap = require('bootstrap')

or:

import bootstrap from 'bootstrap';

We can also install it with Yarn by running:

yarn add bootstrap

RubyGems

It’s also available as Ruby gem.

To install it, we add:

gem 'bootstrap', '~> 5.0.0-alpha1'

to our Gemfile and run bundle install .

We can also run:

gem install bootstrap -v 5.0.0-alpha1

to install it.

Composer

It’s also available as a Composer package. To installing, we run:

composer require twbs/bootstrap:5.0.0-alpha1

NuGet

If we’re using it in a .NET app, we can run:

Install-Package bootstrap

in Powershell.

HTML5 Doctype

The HTML5 doctype is required.

If we don’t have that, then we’ll see weird styling.

So we must write:

<!doctype html>  
<html lang="en">  
  ...  
</html>

Responsive Meta Tag

Bootstrap must have a responsive meta tag to enable it to zoom and scale on all devices.

This must be in the head tag.

So we must write:

<meta name="viewport" content="width=device-width, initial-scale=1">

Box-Sizing

Bootstrap switches from the box-sizing value from content-box to border-box to make sizing easier.

We can override that with:

.foo {  
  box-sizing: content-box;  
}

All child elements including ::before and ::after will inherit the specified box-sizing for .foo .

Reboot

Reboot is used to correct inconsistencies across browsers and devices.

Conclusion

Bootstrap 5 is the upcoming version of Bootstrap.

It removed IE support and lots of other bloat.

There are also many changes in appearance and code.

It’s available as many kinds of packages and also from their CDN.

Categories
JavaScript JavaScript Basics

Introduction to JavaScript Modules

JavaScript modules allow us to divide code into small pieces. They also let us keep some code private while exposing other pieces of code that can be imported into another module.

In this article, we’ll look at how to use define and use them.

Named Exports

Names exports start with the export keyword and they expose items from a module to the outside. Then we can import them somewhere else.

There can be multiple named exports in one module. For instance, we can write:

export const foo = 1;  
export let bar = 1;

Then we can import them into another module as follows:

import { foo, bar } from "./module";
const baz = foo + bar;

We can also import the whole module with the * sign as follows:

import * as module from "./module";
const baz = module.foo + module.bar;

Default Export

We can export one default export using the export default keywords. For instance, we can write:

export default 1;

Then import it as follows:

import foo from "./bar";  
const baz = foo;

We can also export functions and class as follows:

export default () => {};

or:

export default class {}

We don’t need a semicolon at the end of the class export.

Also, we can define default exports with the following code:

const foo = 1;  
export { foo as default };

Browser Differences Between Scripts and Modules

In browsers, scripts are denoted by the script tag. Modules are the same, but it’s denoted by the type attribute with value module .

Scripts are not in strict mode by default, while modules are in strict mode by default.

Top-level variables are global in scrips and are local to the module in modules.

Top-level value of this is window in scripts and it’s undefined in modules.

Scripts are run synchronously while modules are run asynchronously.

There are no import statements in scripts and we can selectively import module members in modules.

We can programmatically import both scripts and modules using promise-based APIs.

Module Characteristics

ES6 modules can be statically analyzed for static checking, optimization, and more. It has a declarative syntax for importing and exporting.

Imports are hoisted to the top so that they can be referenced anywhere in the module.

For instance, if we have:

export const foo = 1;

Then we can import it as follows:

const baz = foo;
import { foo } from "./bar";

Also, they must be at the top-level. Therefore, we can’t have something like:

{  
  import { foo } from "./bar";  
  const baz = foo;  
}

Imports are Read-Only Views on Exports

ES6 imports are read-only views on export entities. Connections to variables inside the module that imported the export remain live.

For instance, if we have:

export const foo = 1;

Then if we have the following:

import { foo } from "./bar";  
foo = 1;

Then we’ll get a ‘”foo” is read-only.’ error since it’s referencing the export directly in a read-only manner.

We get the same result if we change the const to let .

Cyclic Dependencies

If module A and B import members from each other, then we call it a cyclic dependency. This is supported with ES6 modules. For instance, if we have:

index.js :

import { foo } from "./bar";
export const baz = 2;

bar.js :

import { baz } from "./index";
export let foo = 1;

Then the modules are cyclic dependencies since we import a member from bar inindex and we import a member from index in bar .

This works because imports just refer to the original data, so it doesn’t matter when they come from.

Importing Styles

We can import JavaScript modules in various ways. One way is the default import, which is how we import members from a module.

For instance, we can write:

bar.js :

let foo = 1;  
export default foo;

Then we can import it as follows:

import foo from "./bar";

Named imports can be imported as follows. Given the following named exports:

export let foo = 1;

Then we can import it as follows:

import { foo } from "./bar";

We can rename named exports by using the as keyword as follows:

import { foo as baz } from "./bar";

We can also rename default exports as follows:

import { default as foo } from "./bar";

We can also have empty where we don’t import anything. Instead, we run what’s included in the module.

For instance, if we have the following in bar.js :

console.log("bar");

Then we can run the code in bar.js as follows:

import "./bar";

Therefore, we should see 'bar' logged in the console log.

Conclusion

ES6 modules are a great way to divide code into small chunks. We can export module members and import them in another file. Imported members are read-only. Modules are in strict mode by default, so we avoid lots of issues with non-strict mode.

Categories
JavaScript Vue

Adding Getters to a Vuex Store

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add getters to a Vuex store to add states derived from other store states.

Adding Getters to Our Store

We can add a getter to our Vuex store by adding a getters property as follows:

index.js :

const store = new Vuex.Store({  
  state: {  
    todos: [  
      { id: 1, text: "todo1", done: true },  
      { id: 2, text: "todo2", done: false }  
    ]  
  },  
  getters: {  
    doneTodos: state => {  
      return state.todos.filter(todo => todo.done);  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  computed: {  
    doneTodos() {  
      return this.$store.getters.doneTodos;  
    },  
    ...Vuex.mapState({  
      todos: "todos"  
    })  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <div>  
        <p>Todos:</p>  
        <div v-for="todo of todos">{{todo.text}}</div>  
        <p>Done Todos:</p>  
        <div v-for="todo of doneTodos">{{todo.text}}</div>  
      </div>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

In the code, we have a getters object:

getters: {  
  doneTodos: state => {  
    return state.todos.filter(todo => todo.done);  
  }  
}

that derives the state from the todos state.

Then we accessed doneTodos getter by writing:

computed: {  
  doneTodos() {  
    return this.$store.getters.doneTodos;  
  },  
  ...Vuex.mapState({  
    todos: "todos"  
  })  
}

in the Vue instance.

Then the items are rendered in our template and we get:

Todos:todo1  
todo2  
Done Todos:todo1

displayed.

Method-Style Access

We can return a function in a getter function so that we can call the getters from a component with one or more arguments.

For instance, if we want to make a getter that gets a todo by ID, we can write the following:

index.js :

const store = new Vuex.Store({  
  state: {  
    todos: [  
      { id: 1, text: "todo1", done: true },  
      { id: 2, text: "todo2", done: false }  
    ]  
  },  
  getters: {  
    getTodoById: state => id => {  
      return state.todos.find(todo => todo.id === id);  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  computed: Vuex.mapState({  
    todos: "todos"  
  }),  
  methods: {  
    getTodoById(id) {  
      return this.$store.getters.getTodoById(id);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      {{getTodoById(1).text}}  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

Then we see todo1 displayed on the screen.

We have the following code to define our getter:

getters: {  
  getTodoById: state => id => {  
    return state.todos.find(todo => todo.id === id);  
  }  
}

The code above returns a function and then returns a todo with the given ID.

Then we access it in our Vue instance by defining the getTodoById method as follows:

getTodoById(id) {  
  return this.$store.getters.getTodoById(id);  
}

Finally, we render it in our template by writing:

{{getTodoById(1).text}}

The mapGetters Helper

We can use the mapGetters helper to map our getters to computed properties as follows:

index.js :

const store = new Vuex.Store({  
  state: {  
    todos: [  
      { id: 1, text: "todo1", done: true },  
      { id: 2, text: "todo2", done: false }  
    ]  
  },  
  getters: {  
    doneTodosCount: state => {  
      return state.todos.filter(todo => todo.done).length;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  computed: Vuex.mapGetters(["doneTodosCount"])  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.j"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      {{doneTodosCount}}  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

Then we get 1 displayed since we mapped doneTodosCount getter to a computed property with the same name with:

computed: Vuex.mapGetters(["doneTodosCount"])

Conclusion

We can use getters to add states that are derived from another state.

To define a getter, we add a method to the getters property that returns something derived from the state or a function that derives something from a state.

To include it in our components, we can use the this.$store.getters method or use the mapGetters method.

Categories
JavaScript Vue

Adding Mutations to a Vuex Store

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add mutations to a Vuex store so that we can update the store’s state and propagate the change to components that uses the store.

Adding Mutations

We can add a mutation by putting in a function that changes the state by taking it from the parameter and then mutating it.

For example, we can write the following code:

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

Then we can run the mutation function by running:

store.commit('increase')

Then state.count should go up by 1.

We can’t run mutation functions directly.

Commit with Payload

If we want to pass our own argument to it, we can add it after the state parameter, so we write:

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, num) {  
      state.count += num;  
    }  
  }  
});

Then we can commit the mutation by writing:

store.commit('increase', 10)

Then state.count should go up by 10.

The second parameter is usually an object, so we can access property values by writing:

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  }  
});

Then we can commit the mutation by writing:

store.commit('increase', {  
  amount: 10  
})

Any state changes will be observed by components automatically if they access the store from within it.

Vue’s reactivity rules like initializing initial state and using Vue.set to update objects, etc. all applies to Vuex stores.

We can replace strings for mutation names with constants by assigning them to constants.

For example, we can change the example we have above to:

const INCREASE = "increase";

const store = new Vuex.Store({  
  state: {  
    count: 1  
  },  
  mutations: {  
    [INCREASE](state, payload) {  
      state.count += payload.amount;  
    }  
  }  
});

store.commit(INCREASE, { amount: 10 });

JavaScript since ES6 can have dynamic property names, so we can take advantage of that to replace names with constants.

Mutations Must Be Synchronous

Mutations must be synchronous because they’re untrackable since asynchronous code can be called at any time rather than line by line like synchronous code is.

Committing Mutations in Components

We can either use this.$store.commit to commit mutations or we can use Vuex’s helper methods to do so.

this.$store.commit

For example, we can use this.$store.commit as follows:

index.js :

const INCREASE = "increase";const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    [INCREASE](state, payload) {  
      state.count += payload.amount;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  computed: Vuex.mapState(["count"]),  
  methods: {  
    increase(amount) {  
      this.$store.commit(INCREASE, { amount });  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="increase(10)">Increase</button>  
      <p>{{count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

In the code above, we have the increase method as follows:

increase(amount) {  
  this.$store.commit(INCREASE, { amount });  
}

This is called by our template when we click the Increase button.

this.$store.commit(INCREASE, { amount }); will increase state.count by the amount we pass in, which is 10.

Then we get the state back by using mapState so we can display it in our template.

When we click Increase, we get 10, 20, 30, etc.

mapMutations

We can use mapMutations to map mutations to methods in our component as follows:

index.js :

const INCREASE = "increase";

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    [INCREASE](state, payload) {  
      state.count += payload.amount;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  computed: Vuex.mapState(["count"]),  
  methods: {  
    ...Vuex.mapMutations([INCREASE])  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="increase({amount: 10})">Increase</button>  
      <p>{{count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>

In the code above, mapMutations maps the mutation methods to the component methods.

So:

...Vuex.mapMutations([INCREASE])

maps the increase method adds an increase method to the component which calls this.$store.commit(INCREASE) .

INCREASE is the string 'increase' that we have in the first line.

In the template, we just called increase({amount: 10}) when the Increase button is clicked to update state.count in the store.

Conclusion

We add mutations to our Vuex store to update the state of our store.

To do this, we add methods under the mutations object.

Then we can call this.$store.commit or use mapMutations to map mutation methods in the store to component methods.

Mutations must be synchronous since they need to be trackable.