GithubHelp home page GithubHelp logo

flexnav's Introduction

FlexNav

Homework

Examine the files in the other/homework folder. index.html is your starting point and index-done.html the goal. Your assignment is to edit index.html so it matches the goal.

Midterm

Copy the other/homework folder from the previous section (Flexnav) into a stand alone project.

  • index.html is your starting point
  • index-done.html the goal
  • edit index.html so it matches the goal
  • try not to copy directly, use index-done.html only when you get stuck

One task you will have to perform is not in the index-done.html file. Using the notes in Basilica:

  • add an empty div to the page
  • use JavaScript to change the content of the div when the user clicks on the tabs

Make sure to create a local Git repo.

When you are done push your local repo to Github and use Netlify to deploy your assignment.

Send me a link to the Github repo as well as the Netlify site

Reading

The Terminal

There are many good reasons to acquire a basic understanding of the command line terminal. In this class we will use the Terminal app for GIT and GITHUB as well as for Node Package Manager (NPM).


A Note For Windows Users

A rough equivalent to Mac's Terminal app is Powershell but there are important differences. Alternatives to Powershell include the shell that comes with Git for Windows aka "Git Bash." I suggest using Git Bash instead of Powershell on Windows for this exercise.


Some basic shell commands (the convention in documentation is to use $ to indicate a prompt - do NOT include it when copying and pasting a command):

$ pwd  // print working directory
$ cd <path-to-folder>
$ cd .. // go up one level
$ cd ~ // go to your home directory
$ ls  // list files
$ ls -l  // flags expand the command
$ ls -al

Demo: tab completion, history and copy paste.

Demo: on a mac you can cd into a folder via drag and drop or by copying and pasting a folder into the terminal.

$ node --version
$ npm --version
$ git --version
$ node
> var total = 12+12
> total
> var el = document.querySelector('.anything') // error
> .exit // or control + c to exit node
$ clear // or command + k to clear the terminal

Use cd or the copy and paste method to cd into today's exercise folder.

Initialize GIT and Create a Branch

Configure your installation of git:

$ git config --global user.name "John Doe"
$ git config --global user.email [email protected]
$ git config --global init.defaultBranch main
$ git config --list

Initialize your repository:

$ git init
# $ touch .gitignore
# $ echo node_modules >> .gitignore
$ git add .
$ git commit -m 'initial commit'
$ git branch inclass
$ git checkout inclass

Note the .gitignore

Aside - Design Patterns

Today we will be building this simple page. The UI is spare to keep things simple.

Let's examine the samples in other/design-patterns (these are non-trivial examples, you do not need to understand everything, just the basic concepts - static, fragments and SPA or single page application):

  • static/cuisines.html - uses static HTML pages
  • fragments/index-spa-fragments - a single page application with scrolling
  • spa/index-spa-js.html - a single page application with JavaScript

All three approaches are valid and common. For pedagogical purposes we will be modeling our design after the last one - a single page application with JavaScript.


Add a link to styles.css in index.html:

<link rel="stylesheet" href="css/styles.css" />

Add the following to index.html:

<nav>
  <ul>
    <li><a href="index.html">cuisines</a></li>
    <li><a href="chefs.html">chefs</a></li>
    <li><a href="reviews.html">reviews</a></li>
    <li><a href="delivery.html">delivery</a></li>
  </ul>
</nav>

Node Package Manager (NPM)

Node Package Manager is an essential part of the web design and development ecosystem. Node includes NPM as part of its install

In order to familiarize you with node packages and to test your Node installation we will install a server with hot reloading - as opposed to using VS Code's GoLive extension.

Note the presence of package.json in today's folder. Examine it in VS Code.

JSON (JavaScript Object Notation) is a file format often used for transmitting data. It is ubiquitious in web development.

{
  "name": "flex-nav",
  "version": "1.0.0",
  "description": "A simple navbar",
  "main": "index.js",
  "scripts": {
    "start": "browser-sync start --server 'app' --files 'app'"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "browser-sync": "^2.26.0"
  }
}

We are going to recreate this file.

  1. Delete package.json
  2. cd to navigate to today's directory
  3. Then initialize npm and install browser-sync:
$ npm init
$ npm install browser-sync

Note:

  • the installed the software is listed in package.json dependencies (Browser Sync)
  • the addition of the installation folder: node_modules
  • the new package-lock.json
  • the .gitignore file (added by me) declares that the contents of the node_modules folder should not be tracked by git

Examine the contents of node_modules.

Browser Sync is an NPM Package that is developed by a team using Github.

$ npm install browser-sync

Add the script ("browser-sync start --server 'app' --files 'app'") to package.json.

This script is a command line. It was written by consulting the command line documentation.

Make a small change to the HTML and note the hot reloading.

Use ctrl-c to shut down the server.

Try editing the start script to specify the port number:

"scripts": {
  "start": "browser-sync start --port 1234 --server 'app' --files 'app'"
},

Restart the server with $ npm run start.

Flexbox

What is Flexbox?

  • A good reference cheat sheet
  • flex is a display attribute like block, none, inline
  • Do not confuse it with positioning which we have looked at for absolute, relative, static and fixed positioning
  • Get familiar with Can I Use and feature detection

Add and review some basic formatting in app/styles.css:

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
    "Oxygen", "Ubuntu", "Helvetica Neue", Arial, sans-serif,
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
a {
  text-decoration: none;
  color: #333;
}
ul {
  margin: 0;
  padding: 0;
}
nav ul {
  list-style: none;
  background-color: #ffcb2d;
}

Note the complex looking font-family value. It is quite common to use a system font stack that allows each operating system to use its native font. Google it.

You could try font-family: system-ui; but that only works in certain browsers. Consult caniuse.

nav ul {
  ...
  padding-top: 1rem;
  display: flex;
  justify-content: space-around;
  /* background-image: linear-gradient(
    to bottom,
    #ffcb2d 0%,
    #ffcb2d 95%,
    #9b8748 100%
  ); */
}

nav a {
  padding: 4px 8px;
  border: 1px solid #9b8748;
  border-radius: 3px 3px 0 0;
  background-color: #f9eaa9;
  opacity: 0.8;
  /* background-image: linear-gradient(
    to bottom,
    rgba(255, 236, 165, 1) 0%,
    rgba(232, 213, 149, 1) 6%,
    rgba(253, 233, 162, 1) 94%,
    rgba(253, 233, 162, 1) 100%
  ); */

}

nav li {
  display: flex;
}

Add an active class to the first link in the HTML.

nav a:hover,
nav .active {
  background-image: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 1) 0%,
    rgba(224, 226, 240, 1) 6%,
    rgba(254, 254, 254, 1) 53%
  );
  border-bottom: none;
  opacity: 1;
}

Aside: Attribute Selectors

A selector can use HTML tag attributes. nav .active could be written nav a[class="active"] or just [class="active"]

See https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors


We have a meta tag:

<meta name="viewport" content="width=device-width" />
@media (min-width: 460px) {
  nav ul {
    padding-left: 1rem;
    justify-content: flex-start;
  }
  nav li {
    margin-right: 1rem;
  }
}

See this Pen for some basic info on how to control flexbox responsively.

Aside: Flex Order

Flex order property (demo only):

nav :nth-child(2) {
  order: 1;
}

JavaScript Preview & Review - Boulevards de Paris

Recall document.querySelector('<css selector>') returns the first selected item.

Navigate to this Wikipedia article.

Paste the following in the browser's console:

var test = document.querySelector("a");

document.querySelectorAll() returns a collection (nodeList) of the items on the page:

var test = document.querySelectorAll("a");

Inspect one of the listed boulevards to find .mw-category in the code. Note: You can reference the currently selected element using $0 in the console.

var category = document.querySelector(".mw-category");

We can use our category variable as the basis for a more targeted query:

var links = category.querySelectorAll("a");

Examine the methods on the resulting nodeList. Try links.length in the console.

Our nodeList looks like an array but isn't. Let's convert the nodeList into an Array:

var linksArray = Array.from(links);
  • Examine the methods on the resulting Array and compare them to the methods on a nodeList
  • Examine one of the anchor tags from the resulting array in the console. Note the textContent property

Here is a simple example showing how to call an array method and access an element from an array

linksArray[0];
linksArray[0].textContent;

Arrays

We commonly use loops to iterate through an array and perform some action.

Below we initialize an empty array linkText and then loop through the linksArray using its length property. For every item in linksArray we use Array.push() to add it to linkText:

var linkText = [];
for (let i = 0; i < linksArray.length; i++) {
  linkText.push(linksArray[i].textContent);
}

Let's look at a couple important array methods: array.map() and array.filter()

Here's an example that uses the array's map method to isolate the text content from our linksArray:

var linkTextTwo = linksArray.map(function (link) {
  return link.textContent;
});
var linkTextTwo = linksArray
  .map(function (link) {
    return `${link.textContent} is in paree`;
  })
  .join(" AND ");

Let's use another Array method, filter, to isolate only those boulevards that contain a specific string:

var de = linkText.filter(function (streetName) {
  return streetName.includes("de");
});

JavaScript Navigation

We will add an active class to the tabs when they are clicked on.

Link the empty JavaScript file to index.html.

<script src="js/scripts.js"></script>

Add to scripts.js:

var tabs = document.querySelector("nav a");
console.log(tabs);

We need to use querySelectorAll because we are gathering more than one item:

var tabs = document.querySelectorAll("nav a");
console.log(tabs);
console.log(tabs.length);

Now we need to attach an eventListener to each of the tabs. addEventListener() requires you to pass in a specific, individual element to listen to. You cannot pass in an array or node list of matching elements.

var tabs = document.querySelectorAll("nav a");

for (let i = 0; i < tabs.length; i++) {
  tabs[i].addEventListener("click", makeActive);
}

function makeActive(event) {
  console.log(event.target);
  event.preventDefault();
}

Since NodeLists have a forEach method we can also do this:

tabs.forEach(function (tab) {
  tab.addEventListener("click", makeActive);
});

Using an Arrow function shortcut (for anonymous functions):

tabs.forEach((tab) => tab.addEventListener("click", makeActive));

Let's use classList again to add a class to the link we click on:

var tabs = document.querySelectorAll("nav a");

tabs.forEach((tab) => tab.addEventListener("click", makeActive));

function makeActive(event) {
  event.target.classList.add("active");
  event.preventDefault();
}

Lets remove the class from all tabs before we add it so that only one is active at a time:

var tabs = document.querySelectorAll("nav a");

tabs.forEach((tab) => tab.addEventListener("click", makeActive));

function makeActive(event) {
  tabs.forEach((tab) => tab.classList.remove("active"));
  event.target.classList.add("active");
  event.preventDefault();
}

We can separate the class removal out into its own function and then call that function (makeInactive();):

var tabs = document.querySelectorAll("nav a");

tabs.forEach((tab) => tab.addEventListener("click", makeActive));

function makeActive(event) {
  makeInactive();
  event.target.classList.add("active");
  event.preventDefault();
}

function makeInactive() {
  tabs.forEach((tab) => tab.classList.remove("active"));
}

Aside: Prettier

Prettier is a code formatter.

Install the Prettier extension in VS Code.

npm install -D prettier

Create .prettierrc in the app folder.

{
  "singleQuote": true,
  "trailingComma": "none",
  "semi": false
}

And test.

Note: you can also add prettier preferences in VS Code:

"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
  "editor.formatOnSave": true
},
"[html]": {
  "editor.formatOnSave": true
},
"[css]": {
  "editor.formatOnSave": true
},
"prettier.singleQuote": true,
"prettier.trailingComma": "all",

Add some variables with content:

var cuisines =
  "<h1>Cuisines</h1> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis.</p>";

var chefs =
  "<h1>Chefs</h1> <p>Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi.<p>";

var reviews =
  "<h1>Reviews</h1> <p>Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis.</p>";

var delivery =
  "<h1>Delivery</h1> <p>Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi.</p>";

Create an empty div with a class of content in the html:

<div class="content"></div>

Create a reference to it and initialize our page with some text using innerHTML:

var contentPara = document.querySelector('.content');
...
contentPara.innerHTML = cuisines;

Style it using CSS:

.content {
  padding: 1rem;
}

Note that we can access the value of the link's href by using event.target.href:

function makeActive() {
  console.log(event.target.href);
  ...
}

So let's make the content of the .content div depend on the link's href. We will use the string method includes as a test for simple equality will fail:

function makeActive(event) {
  console.log(event.target.href);
  makeInactive();
  event.target.classList.add("active");

  if (event.target.href.includes("chefs")) {
    contentPara.innerHTML = chefs;
  }

  event.preventDefault();
}

Expand the conditions:

function makeActive() {
  makeInactive();
  event.target.classList.add("active");

  if (event.target.href.includes("cuisines")) {
    contentPara.innerHTML = cuisines;
  } else if (event.target.href.includes("chefs")) {
    contentPara.innerHTML = chefs;
  } else if (event.target.href.includes("reviews")) {
    contentPara.innerHTML = reviews;
  } else if (event.target.href.includes("delivery")) {
    contentPara.innerHTML = delivery;
  }

  event.preventDefault();
}

In web development parlance this is something akin to what is known as a Single Page Application or "SPA".

The problem with what we've built might be termed maintaining state and routing. If you refresh the browser while you are on the Reviews tab. The page reinitializes to show the Cuisines tab and content.

Not only is the refresh broken but the back and forward buttons don't work as expected either.

NB: we have a sneaky bug in our code. Everything works but if (event.target.href.includes('cuisines')) will never be true. Can you correct it?

Event Delegation

Instead of listening for clicks on each individual tab:

tabs.forEach(tab => tab.addEventListener('click', makeActive));

We are going to use "event delegation."

Use:

// tabs.forEach((tab) => tab.addEventListener("click", makeActive));
document.addEventListener("click", makeActive);

Everything works but try clicking on the paragraph and the yellow background.

We will use an if statement and the JavaScript "not" (!) operator to ensure that the user has clicked on a link in the navbar before running our code:

function makeActive(event) {
  console.log(event.target);
  if (!event.target.matches("a")) return; // NEW
  makeInactive();
  event.target.classList.add("active");
  if (event.target.href.includes("cuisines")) {
    contentPara.innerHTML = cuisines;
  } else if (event.target.href.includes("chefs")) {
    contentPara.innerHTML = chefs;
  } else if (event.target.href.includes("reviews")) {
    contentPara.innerHTML = reviews;
  } else if (event.target.href.includes("delivery")) {
    contentPara.innerHTML = delivery;
  }
  event.preventDefault();
}

Working with Objects

let obj = {
  a: 1,
  b: 2,
};

console.log(obj.a);

obj.c = 3;

delete obj.a;

Use data-object.js in index.html:

const data = {
  cuisines:
    "<h1>Cuisines</h1> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis.</p>",

  chefs:
    "<h1>Chefs</h1> <p>Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi.<p>",

  reviews:
    "<h1>Reviews</h1> <p>Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis.</p>",

  delivery:
    "<h1>Delivery</h1> <p>Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi.</p>",
};

Reinitialize using "dot" accessor method - e.g. data.cuisines:

contentPara.innerHTML = data.cuisines; // NEW

And use the accessor in the makeActive function:

function makeActive() {
  if (!event.target.matches("nav ul a")) return;
  makeInactive();
  event.target.classList.add("active");
  if (event.target.href.includes("cuisines")) {
    contentPara.innerHTML = data.cuisines; // NEW
  } else if (event.target.href.includes("chefs")) {
    contentPara.innerHTML = data.chefs; // NEW
  } else if (event.target.href.includes("reviews")) {
    contentPara.innerHTML = data.reviews; // NEW
  } else if (event.target.href.includes("delivery")) {
    contentPara.innerHTML = data.delivery; // NEW
  }
  event.preventDefault();
}
...
contentPara.innerHTML = data.cuisines;

Our page is pretty fragile. Hitting refresh still displays the cuisines page and the back button doesn't work. Let's fix it by getting the page contents based on the address in the browser's address bar.

Remove the hardcoded active class in the HTML and replace it with:

document.querySelector("nav a").classList.add("active");

Change the href values to use hashes:

<ul>
  <li><a href="#cuisines">cuisines</a></li>
  <li><a href="#chefs">chefs</a></li>
  <li><a href="#reviews">reviews</a></li>
  <li><a href="#delivery">delivery</a></li>
</ul>

Remove event.preventDefault() from the script. We no longer need it.

Now we'll get the string from the URL using a bit of JavaScript string manipulation:

console.log(window.location);
var type = window.location.hash;
// var type = window.location.hash.substring(1);
console.log(type);
function makeActive(event) {
  if (!event.target.matches("a")) return;
  makeInactive();
  event.target.classList.add("active");
  const type = window.location.hash.substring(1);
  contentPara.innerHTML = data[type];
}

Note the use of data[type] instead of `data.type.

var funkyObject = {
  a: "testing",
  "not a variable": "but you can use it in an object",
};

console.log(funkyObject.a);
console.log( funkyObject.not a variable  ) // doesn't work
console.log(funkyObject["not a variable"]);
var propertyToCheck = prompt("What do you want to get?");
console.log(propertyToCheck);
funkyObject.propertyToCheck; // doesn't work
funkyObject[propertyToCheck];

You have to click on the tab twice to get the right content although the active / inactive class switching works.

We can set the initial hash with window.location.hash = 'cuisines'

See https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event

And then use another event listener hashchange:

var tabs = document.querySelectorAll("nav a");
var contentPara = document.querySelector(".content");

function makeActive(event) {
  if (!event.target.matches("nav a")) return;
  makeInactive();
  event.target.classList.add("active");
  const type = window.location.hash.substring(1);
  contentPara.innerHTML = data[type];
}

function makeInactive() {
  tabs.forEach((tab) => tab.classList.remove("active"));
}

function setContentAccordingToHash() {
  const type = window.location.hash.substring(1);
  contentPara.innerHTML = data[type];
}

function initializePage() {
  document.querySelector("nav a").classList.add("active");
  window.location.hash = "cuisines";
  setContentAccordingToHash();
}

document.addEventListener("click", makeActive);
window.addEventListener("hashchange", setContentAccordingToHash);

initializePage();

Now that we are using a hash we can look for it when the page loads and then derive a solution for the refresh button:

function initializePage() {
  if (!window.location.hash) {
    window.location.hash = "cuisines";
    document.querySelector('[href="#cuisines"]').classList.add("active");
  } else {
    document
      .querySelector('[href="' + window.location.hash + '"] ')
      .classList.add("active");
  }
  setContentAccordingToHash();
}

Note the use of attribute selectors and concatenation.

We'll replace our concatination with template strings (aka string literals).

const name = "Yorik";
const age = 2;
const oldSchool =
  "My dog's name is " + name + " and he is " + age * 7 + " years old.";

const newSchool = `My dog's name is ${name} and he is ${age * 7} years old.`;
console.log("oldschool ", oldschool);
console.log("newschool ", newschool);

Template Strings use back ticks instead of quotes and have access to JS expressions inside placeholders - ${ ... }.

.querySelector(`[href="${window.location.hash}"]`)

If we want to use the hash change to determine both the active tab and the content being displayed we can dispense with the click event listener. This also makes it easier to reset both the active state and content when the browers forward and back arrows are used:

var tabs = document.querySelectorAll("nav a");
contentPara = document.querySelector(".content");

// when the hash changes
function setActiveTabAccordingToHash(type) {
  makeAllTabsInactive();
  var tabToActivate = document.querySelector(`a[href="#${type}"]`);
  tabToActivate.classList.add("active");
}

function makeAllTabsInactive() {
  tabs.forEach((tab) => tab.classList.remove("active"));
}

// runs on page load and whenever the hash changes
function setContentAccordingToHash() {
  var type = window.location.hash.substring(1);
  contentPara.innerHTML = data[type];
  setActiveTabAccordingToHash(type);
}

// only runs once on page load
function initializePage() {
  if (!window.location.hash) {
    window.location.hash = "cuisines";
    document.querySelector('[href="#cuisines"]').classList.add("active");
  }
  setContentAccordingToHash();
}

window.addEventListener("hashchange", setContentAccordingToHash);

initializePage();

An Array of Objects

This is an extremely common format for data to be sent from a server for use in a page.

https://api.nytimes.com/svc/topstories/v2/travel.json?api-key=uQG4jhIEHKHKm0qMKGcTHqUgAolr1GM0`;
https://pokeapi.co/api/v2/pokemon/
https://www.reddit.com/r/BudgetAudiophile.json

Examine data-array.js:

const data = [
  {
    section: "cuisines",
    story:
      "Cuisines. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis.",
  },
  {
    section: "chefs",
    story:
      "Chefs. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi.",
  },
  {
    section: "reviews",
    story:
      "Reviews. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis.",
  },
  {
    section: "delivery",
    story:
      "Delivery. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi.",
  },
];

An array is commonly used in conjunction with loops. We will loop through our data array and se an if statement in order to find a match for our type variable.

function setContentAccordingToHash() {
  const type = window.location.hash.substring(1);
  for (var i = 0; i < data.length; i++) {
    if (data[i].section === type) {
      contentPara.innerHTML = data[i].story;
      setActiveTabAccordingToHash(type);
    }
  }
}

We could also use the array's forEach method instead of a for loop:

function setContentAccordingToHash() {
  const type = window.location.hash.substring(1);
  data.forEach(function (item) {
    if (item.section === type) {
      contentPara.innerHTML = item.story;
      setActiveTabAccordingToHash(type);
    }
  });
}

I prefer a for ... of loop (documentation):

function setContentAccordingToHash() {
  const type = window.location.hash.substring(1);
  for (var item of data) {
    if (item.section === type) {
      contentPara.innerHTML = item.story;
      setActiveTabAccordingToHash(type);
    }
  }
}

We can use a template string (string literal) to create HTML that uses both the section and story elements:

if (item.section === type) {
  // contentPara.innerHTML = item.story
  contentPara.innerHTML = `<h2>${item.section}</h2> <p>${item.story}</p>`;
  setActiveTabAccordingToHash(type);
}

And finally, use an event to kick start our page:

// initializePage()
document.addEventListener("DOMContentLoaded", initializePage);

Notes

Finally, let's create a header for the content.

Use the document.createElement() method to create an element. You can manipulate an element created with createElement() like you would any other element in the DOM. Add classes, attributes, styles, and more.

At the bottom of makeActive:

makeHeader(storyRef);

function makeHeader(head) {
  const myHeader = document.createElement("h3");
  myHeader.innerText = head;
  contentPara.prepend(myHeader);
}

To insert the content we can use:

  • before() - insert an element before another one
  • after() - inserts an element in the DOM after another one
  • prepend() -inserts an element at the beginning of a selection
  • append() - inserts an element at the end

To remove an element you can use remove().

Add some CSS to capitalize the new header:

.content h3 {
  text-transform: capitalize;
}

Add the same CSS property to the tab text.

Initialize on Load

Initialize the header on first load using the load event (or the DOMContentLoaded event).

window.addEventListener('load', setUp);

function setUp() {
  document.querySelector("nav a").classList.add("active");
  contentPara.innerHTML = data[0].story;
  makeHeader(data[0].section);
  window.location.hash = "cuisines";
}

Simulate a click on the first tab:

function setUp() {
  document.querySelector("nav a").click();
}

Notes II

similar. Nice use of callbacks: https://itnext.io/build-a-single-page-web-app-javascript-and-the-dom-90c99b08f8a9

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="icon" type="image//png" href="favicon.png" />
    <link rel="stylesheet" href="css/styles.css" />
  </head>
  <body>
    <nav>
      <ul>
        <li><a href="#cuisines">cuisines</a></li>
        <li><a href="#chefs">chefs</a></li>
        <li><a href="#reviews">reviews</a></li>
        <li><a href="#delivery">delivery</a></li>
      </ul>
    </nav>
    <div class="content"></div>
    <script src="js/data-array.js"></script>
    <script src="js/scripts.js"></script>
  </body>
</html>
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
    "Oxygen", "Ubuntu", "Helvetica Neue", Arial, sans-serif,
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  font-size: 1.25rem;
}
a {
  text-decoration: none;
  color: #333;
}
ul {
  margin: 0;
  padding: 0;
}
nav ul {
  list-style: none;
  background-color: #ffcb2d;
  padding-top: 1rem;
  display: flex;
  justify-content: space-around;
  background-image: linear-gradient(
    to bottom,
    #ffcb2d 0%,
    #ffcb2d 95%,
    #9b8748 100%
  );
}

nav a {
  padding: 4px 8px;
  border: 1px solid #9b8748;
  border-radius: 3px 3px 0 0;
  background-color: #f9eaa9;
  opacity: 0.8;
  background-image: linear-gradient(
    to bottom,
    rgb(248, 236, 193) 0%,
    rgb(245, 237, 213) 6%,
    rgb(248, 219, 112) 94%,
    rgb(247, 204, 51) 100%
  );
}

nav li {
  display: flex;
}

nav a:hover,
nav a[class="active"] {
  background-image: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 1) 0%,
    rgba(224, 226, 240, 1) 6%,
    rgba(254, 254, 254, 1) 53%
  );
  border-bottom: none;
  opacity: 1;
}

.content {
  padding: 1rem;
}

@media (min-width: 460px) {
  nav ul {
    padding-left: 1rem;
    justify-content: flex-start;
  }
  nav li {
    margin-right: 1rem;
  }
}
var tabs = document.querySelectorAll("nav a");
contentPara = document.querySelector(".content");

function setActiveTabAccordingToHash(type) {
  makeAllTabsInactive();
  var tabToActivate = document.querySelector(`a[href="#${type}"]`);
  tabToActivate.classList.add("active");
}

function makeAllTabsInactive() {
  tabs.forEach((tab) => tab.classList.remove("active"));
}

function setContentAccordingToHash() {
  var type = window.location.hash.substring(1);
  for (var item of data) {
    if (item.section === type) {
      contentPara.innerHTML = `<h2>${item.section}</h2> <p>${item.story}</p>`;
      setActiveTabAccordingToHash(type);
    }
  }
}

function initializePage() {
  if (!window.location.hash) {
    window.location.hash = "cuisines";
    document.querySelector('[href="#cuisines"]').classList.add("active");
  }
  setContentAccordingToHash();
}

window.addEventListener("hashchange", setContentAccordingToHash);
document.addEventListener("DOMContentLoaded", initializePage);

flexnav's People

Contributors

dannyboynyc avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.