MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Istoria browserului - History API

La navigarea între pagini, browserul salvează toată istoria navigărilor într-un stivă specială numită history stack. De fiecare dată când browserul încarcă o nouă pagină web sau trece la o legătură pe o pagină web, browserul, implicit, creează o nouă înregistrare în istoria navigărilor. În codul JavaScript, istoria poate fi accesată prin proprietatea history a obiectului window. Această proprietate reprezintă tipul History.

Obiectul History pentru interacțiunea cu istoria navigărilor oferă o serie de metode și proprietăți:

  • Proprietatea length returnează numărul de înregistrări în istoria navigărilor
console.log("În istorie sunt ", history.length, " înregistrări");
  • Proprietatea state returnează înregistrarea curentă din istoria navigărilor. Implicit, la încărcarea primei pagini în browser, această proprietate este null
console.log(history.state);
  • Metoda back() trece la înregistrarea anterioară din istoria navigărilor, similar cu apăsarea butonului Înapoi/Back în browser
history.back(); // navigăm înapoi la pagina anterioară
  • Metoda forward() trece la următoarea pagină vizualizată, similar cu apăsarea butonului Înainte/Forward în browser
history.forward(); // navigăm înainte la următoarea pagină
  • Metoda go() permite navigarea înainte și înapoi în istorie cu un număr specific de pagini. Metodei i se transmite un increment, începând de la pagina web curentă. De exemplu, valoarea -1 duce la deschiderea paginii anterioare, iar valoarea 1 provoacă deschiderea paginii următoare. Dacă se transmite o valoare pentru care în istorie nu există o pagină web corespunzătoare, această metodă nu face nimic. Dacă metoda este apelată fără o valoare sau cu valoarea 0, pagina web curentă se reîncarcă
history.go(-2);     // navigăm înapoi cu 2 pagini
history.go(2);      // navigăm înainte cu 2 pagini
history.go(0);      // reîncărcăm pagina curentă
  • Metoda pushState() adaugă programatic o nouă înregistrare în istoria navigărilor. Acceptă trei parametri:
history.pushState(state, title[, url])
  • Parametrul state reprezintă obiectul adăugat în istoria navigărilor. Poate fi orice obiect de stare
  • Parametrul title stabilește titlul. De notat că browserele pot ignora acest parametru
  • Parametrul url reprezintă adresa URL a noii înregistrări în istorie. Este opțional. Dacă este folosit, adresa URL din acest parametru trebuie să aparțină aceluiași domeniu ca și pagina curentă. Browserul poate seta această adresă ca adresă curentă

Un exemplu simplu:

const state = { url: "/", title: "Home", description: "Pagina Principală" };
// history.pushState(state, state.title);           // fără url
history.pushState(state, state.title, state.url);    // cu url
console.log(state);  // {url: "/", title: "Home", description: "Pagina Principală"}
  • Metoda replaceState() înlocuiește programatic înregistrarea curentă din istoria navigărilor cu o nouă înregistrare. Acceptă aceleași trei parametri:
history.replaceState(state, title, [url])

Un exemplu simplu:

const state = { url: "home", title: "Home", description: "Pagina Principală" };
history.replaceState(state, state.title, state.url);

Evenimentul popstate

De fiecare dată când înregistrarea curentă în istoria vizitelor se schimbă (de exemplu, prin apăsarea butonului "Înapoi" în browser), se declanșează evenimentul popstate. Prin urmare, dacă dorim să gestionăm navigarea prin istoria vizitelor folosind butoanele browserului Înapoi/Înainte, trebuie să gestionăm acest eveniment.

Pentru a gestiona evenimentul popstate, obiectul evenimentului de tip PopStateEvent este transmis handlerului evenimentului. În acest obiect, proprietatea state indică înregistrarea eliminată din istoria vizitelor:

window.addEventListener("popstate", (event) => {  
   console.log(event.state);       // obținem starea anterioară
});

Navigarea pe un site cu o singură pagină

Ca exemplu de utilizare a History API, vom defini cel mai simplu site cu o singură pagină sub forma următoarei pagini web index.html:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <title>FDC.COM</title>
</head>
<body>
<nav><a href="#home">Acasă</a> | <a href="#about">Despre</a> | <a href="#contacts">Contacte</a></nav>
<h1 id="content"></h1>
<script>
// Containerul în care încărcăm conținutul
const contentElement = document.getElementById("content");  
// Obiectul care conține conținutul pentru diferite pagini
const pages = {
   home: { content: "Pagina Principală", url: "#home"},      
   about: { content: "Pagina Despre", url: "#about"  },
   contacts: { content: "Pagina de Contacte", url: "#contacts"}  
};  
// Handlerul pentru click pe linkuri
function handleClick(event){
   // obținem adresa de navigare
   const url = event.target.getAttribute("href");
   // obținem numele paginii, care corespunde cu adresa de navigare
   const pageName = url.split("#").pop();
   // obținem pagina din obiectul pages
   const page = pages[pageName];
   // dacă adresa curentă corespunde cu cea solicitată, ignorăm navigarea
   if(history.state.url != url) {
       contentElement.textContent = page.content;  
       // adăugăm în istorie
       history.pushState(page,  // obiectul state      
           event.target.textContent,   // Titlul      
           event.target.href           // URL    
       );
       document.title = event.target.textContent; // dacă browserul nu setează titlul
   }
   return event.preventDefault();  
}  
// setăm handlerul pentru extragerea stării în History API
window.addEventListener("popstate", (event) => {
   if(event.state)       // dacă există o stare
       contentElement.textContent = event.state.content;   // obținem starea anterioară
});
// setăm handlerul pentru click pe butoane
const links = document.getElementsByTagName("a");
for (let i = 0; i < links.length; i++) {
   links[i].addEventListener("click", handleClick, true);  
}
// implicit încărcăm Pagina Principală
contentElement.textContent = pages.home.content;  
history.pushState(pages.home, "Acasă", pages.home.url);
</script>
</body>
</html>

Inițial, pe pagină avem trei linkuri, pe care, apăsându-le, vom naviga către paginile condiționale:

<nav><a href="#home">Acasă</a> | <a href="#about">Despre</a> | <a href="#contacts">Contacte</a></nav>

Pentru simplitate, presupunem că contextul paginilor condiționale va consta dintr-un singur titlu și va fi încărcat în elementul corespunzător de pe pagină:

<h1 id="content"></h1>

În codul JavaScript, ne vom referi la acest element prin constanta contentElement.

În codul JavaScript, definim conținutul paginilor condiționale sub forma obiectului pages:

const pages = {
   home: { content: "Pagina Principală", url: "#home"},      
   about: { content: "Pagina Despre", url: "#about"  },
   contacts: { content: "Pagina de Contacte", url: "#contacts"}  
};

Fiecare obiect este uniform: conține proprietatea content, care reprezintă conținutul paginii condiționale, și proprietatea url - adresa paginii. De fapt, starea history.state va reprezenta unul dintre aceste obiecte. Dar aici este o condiție importantă - pentru simplitate, numele acestor pagini - home/about/contact corespund cu adresele linkurilor. Se putea desprinde numele, dar asta ar fi dus la creșterea logicii într-un exemplu pur demonstrativ.

Pentru a gestiona apăsarea linkurilor, se definește funcția handleClick, în care se transmite obiectul evenimentului. Și din acest obiect eveniment, prin event.target, putem obține linkul apăsat și datele sale. Astfel, la început, obținem adresa linkului și numele paginii (care corespunde adresei fără slash-ul inițial):

După ce obținem pagina necesară, verificăm ce link a fost apăsat. De exemplu, nu dorim ca, aflându-ne pe o anumită pagină, utilizatorul să reîncarce datele acelei pagini, apăsând din nou același link.

Și pentru acest scop, luăm din istoria navigărilor starea curentă și verificăm proprietatea sa url. Dacă starea curentă (practic, pagina curentă) are aceeași adresă url care este solicitată, atunci nu are sens să reîncărcăm conținutul paginii:

if(history.state.url != url) {

Dacă adresa solicitată este diferită de cea curentă, atunci setăm ca titlu conținutul (proprietatea content) al paginii curente și adăugăm o înregistrare în istoria navigărilor:

contentElement.textContent = page.content;  
// adăugăm în istorie
history.pushState(page,  // obiectul stării      
   event.target.textContent,   // Titlul      
   event.target.href           // URL-ul    
);
document.title = event.target.textContent; // dacă browserul nu setează titlul

Deoarece browserele pot să nu seteze automat titlul, îl setăm manual folosind proprietatea document.title. Astfel, în istoria navigărilor va apărea o înregistrare despre navigarea pe link.

Este de remarcat că, în aplicațiile reale, de obicei, astfel de pagini condiționale sunt definite în fișiere separate și sunt încărcate prin AJAX.

Pentru a gestiona navigările folosind butoanele browserului Înapoi/Înainte, stabilim un handler pentru evenimentul popstate:

window.addEventListener("popstate", (event) => {
   if(event.state)       // dacă există o stare
       contentElement.textContent = event.state.content;   // obținem starea anterioară
});

Aici obținem starea extrată din istoria navigărilor (event.state) și folosind proprietatea sa content, setăm conținutul titlului.

La final, stabilim un handler pentru apăsarea butoanelor:

const links = document.getElementsByTagName("a");
for (let i = 0; i < links.length; i++) {
   links[i].addEventListener("click", handleClick, true);  
}

Și implicit setăm ca pagină curentă condițională obiectul home din obiectul pages, adăugând în același timp o înregistrare corespunzătoare în istoria navigărilor:

contentElement.textContent = pages.home.content;  
history.pushState(pages.home, "Acasă", pages.home.url);

Lansăm pagina web în browser și vom putea naviga pe linkuri ca pe pagini separate.

De asemenea, în locul simbolurilor diez # pentru definirea linkurilor (adică identificatori de fragment), se pot folosi și slash-uri /, care, de exemplu, vor fi mai bune pentru indexarea paginii de către motoarele de căutare. Astfel, exemplul anterior îl putem rescrie în următorul mod:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <title>FDC.COM</title>
</head>
<body>
<nav><a href="/home">Home</a> | <a href="/about">About</a> | <a href="/contacts">Contacts</a></nav>
<h1 id="content"></h1>
<script>
// Containerul în care încărcăm conținutul
const contentElement = document.getElementById("content");  
// Obiectul care conține conținutul pentru diverse pagini
const pages = {
   home: { content: "Pagina de Acasă", url: "/home"},      
   about: { content: "Pagina Despre", url: "/about"  },
   contacts: { content: "Pagina de Contact", url: "/contacts"}  
};  
// Handler pentru click pe linkuri
function handleClick(event){
   // obținem adresa de navigare
   const url = event.target.getAttribute("href");
   // obținem numele paginii, care corespunde cu adresa de navigare
   const pageName = url.split("/").pop();
   // obținem pagina din obiectul pages
   const page = pages[pageName];
   // dacă adresa curentă coincide cu cea solicitată, atunci ignorăm navigarea
   if(history.state.url != url) {
       contentElement.textContent = page.content;  
       // adăugăm în istoric
       history.pushState(page,  // obiectul stării      
           event.target.textContent,   // Titlul      
           event.target.href           // URL-ul    
       );
       document.title = event.target.textContent; // dacă browserul nu setează titlul
   }
   return event.preventDefault();  
}  
// setăm handler pentru extragerea stării în History API
window.addEventListener("popstate", (event) => {
   if(event.state)       // dacă există o stare
       contentElement.textContent = event.state.content;   // obținem starea veche
});
// setăm handler pentru click pe butoane
const links = document.getElementsByTagName("a");
for (let i = 0; i < links.length; i++) {
   links[i].addEventListener("click", handleClick, true);  
}
// implicit încărcăm Pagina de Acasă
contentElement.textContent = pages.home.content;  
history.pushState(pages.home, "Acasă", pages.home.url);
</script>
</body>
</html>

Dar în acest caz, pagina trebuie să fie găzduită pe un server web.

← Lecția anterioară Lecția următoare →