एक नए एपीआई की मदद से, क्लाइंट-साइड रूटिंग को स्टैंडर्ड बनाना. यह एपीआई, एक पेज वाले ऐप्लिकेशन बनाने के तरीके को पूरी तरह से बदल देता है.
एक पेज वाले ऐप्लिकेशन या एसपीए की मुख्य सुविधा यह है कि जब उपयोगकर्ता साइट से इंटरैक्ट करता है, तो उसका कॉन्टेंट डाइनैमिक तरीके से फिर से लिखा जाता है. यह सर्वर से पूरी तरह नए पेज लोड करने के डिफ़ॉल्ट तरीके के बजाय होता है.
एसपीए, History API के ज़रिए या कुछ मामलों में साइट के #hash हिस्से में बदलाव करके, आपको यह सुविधा दे सकते हैं. हालांकि, यह बेहद खराब एपीआई है, जिसे एसपीए के आम तौर पर इस्तेमाल होने से पहले ही डेवलप किया गया था. अब वेब को एक नए तरीके की ज़रूरत है. Navigation API एक ऐसा एपीआई है जिसे प्रस्तावित किया गया है. यह एपीआई, History API की समस्याओं को ठीक करने के बजाय, इस स्पेस को पूरी तरह से बदल देता है. उदाहरण के लिए, स्क्रोल को वापस लाने की सुविधा ने History API को फिर से बनाने के बजाय उसमें पैच किया.
इस पोस्ट में, नेविगेशन एपीआई के बारे में ज़्यादा जानकारी दी गई है. तकनीकी प्रस्ताव पढ़ने के लिए, WICG के डेटा स्टोर में ड्राफ़्ट रिपोर्ट देखें.
इस्तेमाल से जुड़ा उदाहरण
Navigation API का इस्तेमाल करने के लिए, ग्लोबल navigation
ऑब्जेक्ट पर "navigate"
लिसनर जोड़ें.
यह इवेंट मुख्य रूप से केंद्रीकृत होता है: यह सभी तरह के नेविगेशन के लिए ट्रिगर होगा. भले ही, उपयोगकर्ता ने कोई कार्रवाई की हो (जैसे, किसी लिंक पर क्लिक करना, कोई फ़ॉर्म सबमिट करना या पीछे और आगे जाना) या नेविगेशन को प्रोग्राम के हिसाब से ट्रिगर किया गया हो (जैसे, आपकी साइट के कोड के ज़रिए).
ज़्यादातर मामलों में, यह आपके कोड को उस कार्रवाई के लिए ब्राउज़र के डिफ़ॉल्ट व्यवहार को बदलने की अनुमति देता है.
एसपीए के लिए, इसका मतलब है कि उपयोगकर्ता को उसी पेज पर रखना और साइट के कॉन्टेंट को लोड करना या बदलना.
NavigateEvent
को "navigate"
लिसनर को पास किया जाता है. इसमें नेविगेशन की जानकारी होती है, जैसे कि डेस्टिनेशन यूआरएल. साथ ही, इससे आपको नेविगेशन के लिए एक ही जगह से जवाब देने की सुविधा मिलती है.
"navigate"
का बुनियादी दर्शक कुछ ऐसा दिख सकता है:
navigation.addEventListener('navigate', navigateEvent => {
// Exit early if this navigation shouldn't be intercepted.
// The properties to look at are discussed later in the article.
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname === '/') {
navigateEvent.intercept({handler: loadIndexPage});
} else if (url.pathname === '/cats/') {
navigateEvent.intercept({handler: loadCatsPage});
}
});
नेविगेशन को इन दो तरीकों से मैनेज किया जा सकता है:
- नेविगेशन को मैनेज करने के लिए, ऊपर बताए गए तरीके से
intercept({ handler })
को कॉल करना. preventDefault()
को कॉल करना, जो नेविगेशन को पूरी तरह से रद्द कर सकता है.
इस उदाहरण में, इवेंट पर intercept()
को कॉल किया गया है.
ब्राउज़र आपके handler
कॉलबैक को कॉल करता है, जिससे आपकी साइट की अगली स्थिति कॉन्फ़िगर होनी चाहिए.
इससे एक ट्रांज़िशन ऑब्जेक्ट, navigation.transition
बनेगा. इसका इस्तेमाल, नेविगेशन की प्रोग्रेस को ट्रैक करने के लिए, अन्य कोड कर सकते हैं.
आम तौर पर, intercept()
और preventDefault()
, दोनों का इस्तेमाल किया जा सकता है. हालांकि, कुछ मामलों में इनका इस्तेमाल नहीं किया जा सकता.
अगर नेविगेशन क्रॉस-ऑरिजिन नेविगेशन है, तो intercept()
की मदद से नेविगेशन को मैनेज नहीं किया जा सकता.
अगर उपयोगकर्ता अपने ब्राउज़र में 'वापस जाएं' या 'आगे जाएं' बटन दबा रहा है, तो preventDefault()
का इस्तेमाल करके नेविगेशन को रद्द नहीं किया जा सकता. आपको अपनी साइट पर उपयोगकर्ताओं को फ़ंसाने की कोशिश नहीं करनी चाहिए.
(इस बारे में GitHub पर चर्चा की जा रही है.)
भले ही, नेविगेशन को रोका या इंटरसेप्ट न किया जा सके, तब भी "navigate"
इवेंट ट्रिगर होगा.
यह जानकारी देने वाला होता है. उदाहरण के लिए, आपका कोड Analytics इवेंट को लॉग कर सकता है, ताकि यह पता चल सके कि कोई उपयोगकर्ता आपकी साइट छोड़ रहा है.
प्लैटफ़ॉर्म में कोई दूसरा इवेंट क्यों जोड़ना चाहिए?
"navigate"
इवेंट लिसनर, एसपीए में यूआरएल में होने वाले बदलावों को मैनेज करता है.
पुराने एपीआई का इस्तेमाल करके, ऐसा करना मुश्किल है.
अगर आपने कभी History API का इस्तेमाल करके, अपने एसपीए के लिए रूटिंग लिखी है, तो हो सकता है कि आपने ऐसा कोड जोड़ा हो:
function updatePage(event) {
event.preventDefault(); // we're handling this link
window.history.pushState(null, '', event.target.href);
// TODO: set up page based on new URL
}
const links = [...document.querySelectorAll('a[href]')];
links.forEach(link => link.addEventListener('click', updatePage));
यह ठीक है, लेकिन इसमें सभी जानकारी नहीं है. आपके पेज पर लिंक आ सकते हैं और हट सकते हैं. साथ ही, उपयोगकर्ताओं के पास पेजों पर नेविगेट करने के लिए, लिंक के अलावा और भी तरीके होते हैं. उदाहरण के लिए, वे कोई फ़ॉर्म सबमिट कर सकते हैं या इमेज मैप का इस्तेमाल भी कर सकते हैं. आपका पेज इनसे निपट सकता है, लेकिन कई ऐसी संभावनाएं हैं जिन्हें आसान बनाया जा सकता है. ऐसा करने में, नया नेविगेशन एपीआई आपकी मदद करता है.
इसके अलावा, ऊपर दिया गया तरीका, बैक/फ़ॉरवर्ड नेविगेशन को मैनेज नहीं करता. इसके लिए, "popstate"
नाम का एक और इवेंट है.
निजी तौर पर, मुझे लगता है कि History API से इन संभावनाओं को पूरा करने में मदद मिल सकती है.
हालांकि, इसमें सिर्फ़ दो काम होते हैं: उपयोगकर्ता के ब्राउज़र में बैक या फ़ॉरवर्ड बटन दबाने पर जवाब देना. साथ ही, यूआरएल को पुश और बदलना.
इसकी तुलना "navigate"
से नहीं की जा सकती. हालांकि, अगर आपने क्लिक इवेंट के लिए मैन्युअल रूप से लिसनर सेट अप किए हैं, तो इसकी तुलना "navigate"
से की जा सकती है. उदाहरण के लिए, ऊपर दिखाया गया है.
नेविगेशन को मैनेज करने का तरीका तय करना
navigateEvent
में नेविगेशन के बारे में काफ़ी जानकारी होती है. इसका इस्तेमाल करके, किसी नेविगेशन को मैनेज करने का तरीका तय किया जा सकता है.
मुख्य प्रॉपर्टी ये हैं:
canIntercept
- अगर यह गलत है, तो नेविगेशन को इंटरसेप्ट नहीं किया जा सकता. क्रॉस-ऑरिजिन नेविगेशन और क्रॉस-दस्तावेज़ ट्रैवल को इंटरसेप्ट नहीं किया जा सकता.
destination.url
- नेविगेशन को मैनेज करते समय, यह सबसे अहम जानकारी हो सकती है.
hashChange
- अगर नेविगेशन एक ही दस्तावेज़ में है और यूआरएल का सिर्फ़ हैश हिस्सा मौजूदा यूआरएल से अलग है, तो यह वैल्यू 'सही' होगी.
आधुनिक एसपीए में, हैश को मौजूदा दस्तावेज़ के अलग-अलग हिस्सों से लिंक करने के लिए इस्तेमाल किया जाना चाहिए. इसलिए, अगर
hashChange
सही है, तो हो सकता है कि आपको इस नेविगेशन को इंटरसेप्ट करने की ज़रूरत न पड़े. downloadRequest
- अगर यह सही है, तो नेविगेशन की शुरुआत
download
एट्रिब्यूट वाले लिंक से हुई थी. ज़्यादातर मामलों में, आपको इसकी जांच करने की ज़रूरत नहीं होती. formData
- अगर यह शून्य नहीं है, तो यह नेविगेशन, POST फ़ॉर्म सबमिशन का हिस्सा है.
नेविगेशन को मैनेज करते समय, इस बात का ध्यान रखें.
अगर आपको सिर्फ़ GET नेविगेशन मैनेज करने हैं, तो उन नेविगेशन को इंटरसेप्ट करने से बचें जिनमें
formData
शून्य नहीं है. इस लेख में आगे, फ़ॉर्म सबमिशन को मैनेज करने का उदाहरण देखें. navigationType
- यह
"reload"
,"push"
,"replace"
या"traverse"
में से कोई एक है. अगर यह"traverse"
है, तोpreventDefault()
की मदद से इस नेविगेशन को रद्द नहीं किया जा सकता.
उदाहरण के लिए, पहले उदाहरण में इस्तेमाल किया गया shouldNotIntercept
फ़ंक्शन कुछ ऐसा हो सकता है:
function shouldNotIntercept(navigationEvent) {
return (
!navigationEvent.canIntercept ||
// If this is just a hashChange,
// just let the browser handle scrolling to the content.
navigationEvent.hashChange ||
// If this is a download,
// let the browser perform the download.
navigationEvent.downloadRequest ||
// If this is a form submission,
// let that go to the server.
navigationEvent.formData
);
}
इंटरसेप्ट करना
जब आपका कोड अपने "navigate"
लिसनर से intercept({ handler })
को कॉल करता है, तो यह ब्राउज़र को बताता है कि वह अब पेज को नई और अपडेट की गई स्थिति के लिए तैयार कर रहा है. साथ ही, यह भी बताता है कि नेविगेशन में कुछ समय लग सकता है.
ब्राउज़र, मौजूदा स्थिति के लिए स्क्रोल की पोज़िशन कैप्चर करके शुरू होता है, ताकि इसे बाद में वैकल्पिक रूप से वापस लाया जा सके. इसके बाद, यह आपके handler
कॉलबैक को कॉल करता है.
अगर आपका handler
कोई प्रॉमिस दिखाता है (जो असाइन किए गए फ़ंक्शन के साथ अपने-आप होता है), तो वह प्रॉमिस ब्राउज़र को बताता है कि नेविगेशन में कितना समय लगेगा और वह पूरा हुआ या नहीं.
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
},
});
}
});
इसलिए, यह एपीआई एक ऐसा सेमैनटिक कॉन्सेप्ट पेश करता है जिसे ब्राउज़र समझता है: फ़िलहाल, एसपीए नेविगेशन हो रहा है, जो समय के साथ दस्तावेज़ को पिछले यूआरएल और स्टेटस से नए में बदल रहा है. इससे कई फ़ायदे मिल सकते हैं. जैसे, ब्राउज़र किसी नेविगेशन की शुरुआत, आखिर या संभावित गड़बड़ी को दिखा सकते हैं. उदाहरण के लिए, Chrome अपने नेटिव लोडिंग इंडिकेटर को चालू करता है और उपयोगकर्ता को 'रोकें' बटन से इंटरैक्ट करने की अनुमति देता है. (फ़िलहाल, जब उपयोगकर्ता पीछे/आगे बटन का इस्तेमाल करके नेविगेट करता है, तो ऐसा नहीं होता. हालांकि, यह समस्या जल्द ही ठीक कर दी जाएगी.)
नेविगेशन की जानकारी सेव की जा रही है
नेविगेशन को इंटरसेप्ट करते समय, नया यूआरएल आपके handler
कॉलबैक के कॉल होने से ठीक पहले लागू हो जाएगा.
अगर डीओएम को तुरंत अपडेट नहीं किया जाता है, तो एक ऐसी अवधि बन जाती है जब नए यूआरएल के साथ पुराना कॉन्टेंट भी दिखता है.
इससे डेटा फ़ेच करने या नए सब-रिसॉर्स लोड करने के दौरान, रिलेटिव यूआरएल रिज़ॉल्यूशन जैसी चीज़ों पर असर पड़ता है.
यूआरएल में बदलाव करने में देरी करने के तरीके के बारे में GitHub पर चर्चा की जा रही है. हालांकि, आम तौर पर हमारा सुझाव है कि आने वाले कॉन्टेंट के लिए, पेज को तुरंत किसी तरह के प्लेसहोल्डर से अपडेट करें:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
// The URL has already changed, so quickly show a placeholder.
renderArticlePagePlaceholder();
// Then fetch the real data.
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
},
});
}
});
इससे, यूआरएल रिज़ॉल्यूशन से जुड़ी समस्याओं से बचा जा सकता है. साथ ही, उपयोगकर्ता को तुरंत जवाब मिल जाता है, जिससे उन्हें तेज़ी का एहसास होता है.
रोकने के सिग्नल
intercept()
हैंडलर में, एक साथ कई काम किए जा सकते हैं. इसलिए, हो सकता है कि नेविगेशन की ज़रूरत न पड़े.
ऐसा तब होता है, जब:
- उपयोगकर्ता किसी दूसरे लिंक पर क्लिक करता है या कोई कोड किसी दूसरे नेविगेशन पर ले जाता है. इस मामले में, पुराने नेविगेशन को नए नेविगेशन के लिए छोड़ दिया जाता है.
- उपयोगकर्ता, ब्राउज़र में 'रोकें' बटन पर क्लिक करता है.
इनमें से किसी भी संभावना से निपटने के लिए, "navigate"
लिस्नर को पास किए गए इवेंट में एक signal
प्रॉपर्टी होती है, जो एक AbortSignal
होती है.
ज़्यादा जानकारी के लिए, फ़ेच को बीच में रोकना लेख पढ़ें.
कम शब्दों में कहें, तो यह एक ऐसा ऑब्जेक्ट उपलब्ध कराता है जो आपके काम को रोकने पर इवेंट ट्रिगर करता है.
ध्यान दें कि fetch()
पर किए जाने वाले किसी भी कॉल में AbortSignal
पास किया जा सकता है. इससे, नेविगेशन के पहले होने पर, इन-फ़्लाइट नेटवर्क अनुरोध रद्द हो जाएंगे.
इससे उपयोगकर्ता के बैंडविड्थ को बचाने के साथ-साथ, fetch()
से दिखाए गए Promise
को अस्वीकार किया जा सकेगा. साथ ही, बाद में आने वाले कोड को, DOM को अपडेट करने जैसी कार्रवाइयों से रोका जा सकेगा, ताकि अब अमान्य पेज नेविगेशन न दिखाया जा सके.
यहां पिछला उदाहरण दिया गया है, लेकिन इसमें getArticleContent
को इनलाइन किया गया है. इससे पता चलता है कि AbortSignal
का इस्तेमाल fetch()
के साथ कैसे किया जा सकता है:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
// The URL has already changed, so quickly show a placeholder.
renderArticlePagePlaceholder();
// Then fetch the real data.
const articleContentURL = new URL(
'/get-article-content',
location.href
);
articleContentURL.searchParams.set('path', url.pathname);
const response = await fetch(articleContentURL, {
signal: navigateEvent.signal,
});
const articleContent = await response.json();
renderArticlePage(articleContent);
},
});
}
});
स्क्रोल करने की सुविधा
किसी नेविगेशन को intercept()
करने पर, ब्राउज़र अपने-आप स्क्रोल करने की कोशिश करेगा.
जब navigationEvent.navigationType
"push"
या "replace"
हो, तो इतिहास की किसी नई एंट्री पर जाने के लिए, इसका मतलब है कि यूआरएल फ़्रैगमेंट (#
के बाद का हिस्सा) से दिखाए गए हिस्से पर स्क्रोल करने की कोशिश की जा रही है या स्क्रोल को पेज के सबसे ऊपर रीसेट किया जा रहा है.
फिर से लोड करने और ट्रैवर्स करने के लिए, इसका मतलब है कि स्क्रोल की उस स्थिति को वापस लाना जहां यह इतिहास एंट्री आखिरी बार दिखाई गई थी.
डिफ़ॉल्ट रूप से, ऐसा तब होता है, जब आपके handler
से मिला प्रॉमिस पूरा हो जाता है. हालांकि, अगर आपको पहले स्क्रोल करना है, तो navigateEvent.scroll()
को कॉल किया जा सकता है:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
navigateEvent.scroll();
const secondaryContent = await getSecondaryContent(url.pathname);
addSecondaryContent(secondaryContent);
},
});
}
});
इसके अलावा, intercept()
के scroll
विकल्प को "manual"
पर सेट करके, ऑटो स्क्रोल मैनेजमेंट से पूरी तरह ऑप्ट आउट किया जा सकता है:
navigateEvent.intercept({
scroll: 'manual',
async handler() {
// …
},
});
फ़ोकस मैनेज करना
आपके handler
से मिलने वाला प्रॉमिस पूरा होने के बाद, ब्राउज़र autofocus
एट्रिब्यूट सेट वाले पहले एलिमेंट पर फ़ोकस करेगा. अगर किसी भी एलिमेंट में वह एट्रिब्यूट नहीं है, तो <body>
एलिमेंट पर फ़ोकस करेगा.
इस सुविधा से ऑप्ट आउट करने के लिए, intercept()
के focusReset
विकल्प को "manual"
पर सेट करें:
navigateEvent.intercept({
focusReset: 'manual',
async handler() {
// …
},
});
फ़ीड इंपोर्ट होने और न होने के इवेंट
जब आपके intercept()
हैंडलर को कॉल किया जाता है, तो इनमें से कोई एक चीज़ होगी:
- अगर लौटाया गया
Promise
पूरा हो जाता है या आपनेintercept()
को कॉल नहीं किया है, तो Navigation API,Event
के साथ"navigatesuccess"
को ट्रिगर करेगा. - अगर लौटाया गया
Promise
अस्वीकार कर दिया जाता है, तो एपीआईErrorEvent
के साथ"navigateerror"
को ट्रिगर करेगा.
इन इवेंट की मदद से, आपके कोड को एक ही जगह से सफलता या असफलता से निपटने में मदद मिलती है. उदाहरण के लिए, पहले दिखाए गए प्रोग्रेस इंडिकेटर को छिपाकर, सफलता का पता लगाया जा सकता है. जैसे:
navigation.addEventListener('navigatesuccess', event => {
loadingIndicator.hidden = true;
});
इसके अलावा, गड़बड़ी होने पर, गड़बड़ी का मैसेज भी दिखाया जा सकता है:
navigation.addEventListener('navigateerror', event => {
loadingIndicator.hidden = true; // also hide indicator
showMessage(`Failed to load page: ${event.message}`);
});
"navigateerror"
इवेंट लिसनर, जिसे ErrorEvent
मिलता है, खास तौर पर काम का होता है. इसकी वजह यह है कि यह आपके उस कोड से कोई भी गड़बड़ी ज़रूर पाएगा जो नया पेज सेट अप कर रहा है.
await fetch()
यह जानकर कि अगर नेटवर्क उपलब्ध नहीं है, तो गड़बड़ी को "navigateerror"
पर भेजा जाएगा.
नेविगेशन एंट्री
navigation.currentEntry
, मौजूदा एंट्री का ऐक्सेस देता है.
यह एक ऐसा ऑब्जेक्ट है जो बताता है कि उपयोगकर्ता फ़िलहाल कहां है.
इस एंट्री में मौजूदा यूआरएल, मेटाडेटा, और डेवलपर की दी गई स्थिति शामिल होती है. मेटाडेटा का इस्तेमाल, समय के साथ इस एंट्री की पहचान करने के लिए किया जा सकता है.
मेटाडेटा में key
शामिल होता है, जो हर एंट्री की यूनीक स्ट्रिंग प्रॉपर्टी होती है. यह मौजूदा एंट्री और उसके स्लॉट को दिखाती है.
मौजूदा एंट्री का यूआरएल या स्टेटस बदलने पर भी, यह कुंजी एक जैसी रहती है.
यह अब भी उसी स्लॉट में है.
इसके उलट, अगर कोई उपयोगकर्ता 'वापस जाएं' बटन दबाकर, उसी पेज को फिर से खोलता है, तो key
बदल जाएगा, क्योंकि इस नई एंट्री से नया स्लॉट बन जाएगा.
डेवलपर के लिए, key
फ़ंक्शन काफ़ी काम का है. इसकी मदद से, नेविगेशन एपीआई की मदद से उपयोगकर्ता को सीधे मैच करने वाली कुंजी वाली एंट्री पर ले जाया जा सकता है.
पेजों के बीच आसानी से जाने के लिए, अन्य एंट्री की स्थितियों में भी इसे होल्ड किया जा सकता है.
// On JS startup, get the key of the first loaded page
// so the user can always go back there.
const {key} = navigation.currentEntry;
backToHomeButton.onclick = () => navigation.traverseTo(key);
// Navigate away, but the button will always work.
await navigation.navigate('/another_url').finished;
स्थिति
नेविगेशन एपीआई, "स्टेटस" की जानकारी दिखाता है. यह डेवलपर की दी गई जानकारी होती है, जो मौजूदा इतिहास की एंट्री में हमेशा सेव रहती है. हालांकि, यह जानकारी उपयोगकर्ता को सीधे तौर पर नहीं दिखती.
यह History API में मौजूद history.state
से काफ़ी मिलता-जुलता है, लेकिन इसमें कुछ सुधार किए गए हैं.
Navigation API में, मौजूदा एंट्री (या किसी भी एंट्री) की स्थिति की कॉपी दिखाने के लिए, .getState()
तरीके को कॉल किया जा सकता है:
console.log(navigation.currentEntry.getState());
डिफ़ॉल्ट रूप से, यह undefined
होगा.
सेटिंग की स्थिति
स्टेटस ऑब्जेक्ट में बदलाव किया जा सकता है, लेकिन उन बदलावों को इतिहास की एंट्री के साथ सेव नहीं किया जाता. इसलिए:
const state = navigation.currentEntry.getState();
console.log(state.count); // 1
state.count++;
console.log(state.count); // 2
// But:
console.info(navigation.currentEntry.getState().count); // will still be 1
स्क्रिप्ट नेविगेशन के दौरान, स्टेटस सेट करने का सही तरीका:
navigation.navigate(url, {state: newState});
// Or:
navigation.reload({state: newState});
यहां newState
, क्लोन किया जा सकने वाला कोई भी ऑब्जेक्ट हो सकता है.
अगर आपको मौजूदा एंट्री की स्थिति अपडेट करनी है, तो मौजूदा एंट्री की जगह ले लेने वाला नेविगेशन इस्तेमाल करना सबसे अच्छा है:
navigation.navigate(location.href, {state: newState, history: 'replace'});
इसके बाद, आपका "navigate"
इवेंट लिसनर, navigateEvent.destination
की मदद से इस बदलाव को पिक अप कर सकता है:
navigation.addEventListener('navigate', navigateEvent => {
console.log(navigateEvent.destination.getState());
});
सिंक करके स्टेटस अपडेट करना
आम तौर पर, navigation.reload({state: newState})
के ज़रिए स्टेटस को असिंक्रोनस तरीके से अपडेट करना बेहतर होता है. इसके बाद, आपका "navigate"
लिसनर उस स्टेटस को लागू कर सकता है. हालांकि, कभी-कभी ऐसा होता है कि जब आपका कोड इस बारे में जानता है, तब तक स्टेटस में बदलाव पहले ही लागू हो चुका होता है. जैसे, जब कोई उपयोगकर्ता किसी <details>
एलिमेंट को टॉगल करता है या किसी फ़ॉर्म इनपुट की स्थिति बदलता है. ऐसे मामलों में, आपको स्टेट को अपडेट करना पड़ सकता है, ताकि रीलोड और ट्रैवर्स के दौरान ये बदलाव बरकरार रहें. updateCurrentEntry()
का इस्तेमाल करके ऐसा किया जा सकता है:
navigation.updateCurrentEntry({state: newState});
इस बदलाव के बारे में जानने के लिए, एक इवेंट भी आयोजित किया गया है:
navigation.addEventListener('currententrychange', () => {
console.log(navigation.currentEntry.getState());
});
हालांकि, अगर आपको "currententrychange"
में स्टेटस में हुए बदलावों पर प्रतिक्रिया देनी पड़ रही है, तो हो सकता है कि आपने "navigate"
इवेंट और "currententrychange"
इवेंट के बीच, स्टेटस मैनेज करने वाले कोड को बांटा हो या उसकी डुप्लीकेट कॉपी बनाई हो. वहीं, navigation.reload({state: newState})
की मदद से, इसे एक ही जगह पर मैनेज किया जा सकता है.
स्टेटस बनाम यूआरएल पैरामीटर
स्टेट को स्ट्रक्चर्ड ऑब्जेक्ट के तौर पर इस्तेमाल किया जा सकता है. इसलिए, अपने ऐप्लिकेशन की सभी स्टेट के लिए इसका इस्तेमाल किया जा सकता है. हालांकि, ज़्यादातर मामलों में उस स्थिति को यूआरएल में सेव करना बेहतर होता है.
अगर आपको लगता है कि उपयोगकर्ता के किसी दूसरे उपयोगकर्ता के साथ यूआरएल शेयर करने पर, स्टेटस को बनाए रखा जाना चाहिए, तो उसे यूआरएल में सेव करें. अगर ऐसा नहीं है, तो स्टेटस ऑब्जेक्ट बेहतर विकल्प है.
सभी एंट्री ऐक्सेस करना
हालांकि, "मौजूदा एंट्री" में और भी चीज़ें शामिल होती हैं.
एपीआई, navigation.entries()
कॉल के ज़रिए आपकी साइट का इस्तेमाल करते समय, उपयोगकर्ता ने जिन एंट्री पर नेविगेट किया है उनकी पूरी सूची को ऐक्सेस करने का भी तरीका उपलब्ध कराता है. यह कॉल, एंट्री का स्नैपशॉट कलेक्शन दिखाता है.
इसका इस्तेमाल, उदाहरण के लिए, उपयोगकर्ता के किसी पेज पर नेविगेट करने के तरीके के आधार पर अलग यूज़र इंटरफ़ेस दिखाने के लिए किया जा सकता है. इसके अलावा, इसका इस्तेमाल पिछले यूआरएल या उनकी स्थितियों को देखने के लिए भी किया जा सकता है.
मौजूदा History API की मदद से, ऐसा नहीं किया जा सकता.
अलग-अलग NavigationHistoryEntry
पर "dispose"
इवेंट को भी सुना जा सकता है. यह इवेंट तब ट्रिगर होता है, जब एंट्री ब्राउज़र इतिहास का हिस्सा नहीं रहती. ऐसा सामान्य तौर पर डेटा मिटाने के दौरान हो सकता है. हालांकि, नेविगेट करने के दौरान भी ऐसा हो सकता है. उदाहरण के लिए, अगर आपने 10 जगहों पर वापस जाने के बाद आगे की ओर नेविगेट किया, तो इतिहास की उन 10 एंट्री को हटा दिया जाएगा.
उदाहरण
ऊपर बताए गए सभी तरह के नेविगेशन के लिए, "navigate"
इवेंट ट्रिगर होता है.
(असल में, सभी तरह के स्पेसिफ़िकेशन में एक लंबा अध्याय होता है.)
कई साइटों के लिए, सबसे सामान्य मामला तब होगा, जब उपयोगकर्ता <a href="...">
पर क्लिक करेगा. हालांकि, नेविगेशन के दो ऐसे टाइप हैं जिनके बारे में बताना ज़रूरी है. ये टाइप ज़्यादा जटिल हैं.
प्रोग्रामैटिक नेविगेशन
पहला है प्रोग्रामैटिक नेविगेशन, जहां नेविगेशन आपके क्लाइंट-साइड कोड में मौजूद किसी तरीके के कॉल की वजह से होता है.
नेविगेशन के लिए, अपने कोड में कहीं से भी navigation.navigate('/another_page')
को कॉल किया जा सकता है.
इसे "navigate"
लिसनर पर रजिस्टर किए गए सेंट्रलाइज़्ड इवेंट लिसनर से मैनेज किया जाएगा. साथ ही, आपके सेंट्रलाइज़्ड लिसनर को सिंक्रोनस तरीके से कॉल किया जाएगा.
इसका मकसद, location.assign()
और दोस्तों जैसे पुराने तरीकों के साथ-साथ, History API के तरीकों pushState()
और replaceState()
को बेहतर तरीके से एग्रीगेट करना है.
navigation.navigate()
तरीका, एक ऑब्जेक्ट दिखाता है, जिसमें { committed, finished }
में दो Promise
इंस्टेंस होते हैं.
इससे, ट्रांज़िशन के "हो जाने" (दिखने वाला यूआरएल बदल गया है और नया NavigationHistoryEntry
उपलब्ध है) या "पूरा हो जाने" (intercept({ handler })
से मिले सभी प्रॉमिस पूरे हो गए हैं या किसी दूसरे नेविगेशन की वजह से अस्वीकार कर दिए गए हैं) तक इंतज़ार किया जा सकता है.
navigate
तरीके में एक विकल्प ऑब्जेक्ट भी होता है, जहां ये सेट किए जा सकते हैं:
state
: इतिहास की नई एंट्री की स्थिति, जोNavigationHistoryEntry
पर.getState()
तरीके से उपलब्ध है.history
: इसे"replace"
पर सेट करके, इतिहास की मौजूदा एंट्री को बदला जा सकता है.info
:navigateEvent.info
के ज़रिए नेविगेट इवेंट को पास करने के लिए ऑब्जेक्ट.
उदाहरण के लिए, info
का इस्तेमाल किसी खास ऐनिमेशन को दिखाने के लिए किया जा सकता है, जिससे अगला पेज दिखता है.
(इसके अलावा, ग्लोबल वैरिएबल सेट किया जा सकता है या इसे #hash के हिस्से के तौर पर शामिल किया जा सकता है. दोनों विकल्प थोड़े अजीब हैं.)
ध्यान दें कि अगर उपयोगकर्ता बाद में नेविगेशन करता है, तो यह info
फिर से नहीं चलेगा. उदाहरण के लिए, 'वापस जाएं' और 'आगे जाएं' बटन का इस्तेमाल करके.
असल में, ऐसे मामलों में यह हमेशा undefined
होगा.
navigation
में नेविगेट करने के कई अन्य तरीके भी हैं. ये सभी तरीके, { committed, finished }
वाला ऑब्जेक्ट दिखाते हैं.
मैंने पहले ही traverseTo()
और navigate()
के बारे में बताया है. traverseTo()
में key
को स्वीकार किया जाता है, जो उपयोगकर्ता के इतिहास में किसी खास एंट्री को दिखाता है.
इसमें back()
, forward()
, और reload()
भी शामिल हैं.
इन सभी तरीकों को, navigate()
की तरह ही, एक ही जगह पर मौजूद "navigate"
इवेंट लिसनर मैनेज करता है.
फ़ॉर्म सबमिशन
दूसरी बात, POST के ज़रिए एचटीएमएल <form>
सबमिशन एक खास तरह का नेविगेशन है और Navigation API इसे इंटरसेप्ट कर सकता है.
इसमें एक अतिरिक्त पेलोड शामिल होता है, लेकिन नेविगेशन को अब भी "navigate"
लिसनर से मैनेज किया जाता है.
NavigateEvent
पर formData
प्रॉपर्टी देखकर, फ़ॉर्म सबमिशन का पता लगाया जा सकता है.
यहां एक उदाहरण दिया गया है, जिसमें किसी भी फ़ॉर्म सबमिशन को fetch()
की मदद से, मौजूदा पेज पर ही रहने वाले फ़ॉर्म में बदल दिया गया है:
navigation.addEventListener('navigate', navigateEvent => {
if (navigateEvent.formData && navigateEvent.canIntercept) {
// User submitted a POST form to a same-domain URL
// (If canIntercept is false, the event is just informative:
// you can't intercept this request, although you could
// likely still call .preventDefault() to stop it completely).
navigateEvent.intercept({
// Since we don't update the DOM in this navigation,
// don't allow focus or scrolling to reset:
focusReset: 'manual',
scroll: 'manual',
handler() {
await fetch(navigateEvent.destination.url, {
method: 'POST',
body: navigateEvent.formData,
});
// You could navigate again with {history: 'replace'} to change the URL here,
// which might indicate "done"
},
});
}
});
क्या अनुपलब्ध है?
"navigate"
इवेंट लिसनर को एक ही जगह से कंट्रोल किया जाता है. इसके बावजूद, मौजूदा नेविगेशन एपीआई स्पेसिफ़िकेशन, पेज के पहली बार लोड होने पर "navigate"
को ट्रिगर नहीं करता.
जिन साइटों के लिए सभी स्टेटस में सर्वर साइड रेंडरिंग (एसएसआर) का इस्तेमाल किया जाता है उनके लिए यह ठीक हो सकता है—आपका सर्वर सही शुरुआती स्टेटस दिखा सकता है. यह आपके उपयोगकर्ताओं को कॉन्टेंट दिखाने का सबसे तेज़ तरीका है.
हालांकि, जो साइटें अपने पेज बनाने के लिए क्लाइंट-साइड कोड का इस्तेमाल करती हैं उन्हें अपने पेज को शुरू करने के लिए, एक और फ़ंक्शन बनाना पड़ सकता है.
नेविगेशन एपीआई के डिज़ाइन में एक और खास बात यह है कि यह सिर्फ़ एक फ़्रेम में काम करता है. जैसे, टॉप-लेवल पेज या कोई खास <iframe>
.
इस बदलाव से कई दिलचस्प नतीजे मिलेंगे. इनके बारे में स्पेसिफ़िकेशन में ज़्यादा जानकारी दी गई है. हालांकि, इसका असर यह होगा कि डेवलपर को कम परेशानी होगी.
पिछले History API में, फ़्रेम के लिए सहायता जैसे कई मुश्किल मामले हैं. हालांकि, नए Navigation API में इन मुश्किल मामलों को शुरू से ही हैंडल किया जाता है.
आखिर में, प्रोग्राम के हिसाब से उन एंट्री की सूची में बदलाव करने या उन्हें फिर से व्यवस्थित करने के बारे में अभी तक कोई सहमति नहीं बनी है जिन पर उपयोगकर्ता ने नेविगेट किया है. इस बारे में फ़िलहाल चर्चा की जा रही है. हालांकि, एक विकल्प यह हो सकता है कि सिर्फ़ पुरानी एंट्री या "आने वाले समय में की जाने वाली सभी एंट्री" को मिटाने की अनुमति दी जाए. बाद वाले विकल्प से, कुछ समय के लिए स्टेटस सेट किया जा सकता है. उदाहरण के लिए, डेवलपर के तौर पर, मेरे पास ये काम करने का विकल्प है:
- नए यूआरएल या स्टेटस पर जाकर, उपयोगकर्ता से सवाल पूछना
- उपयोगकर्ता को अपना काम पूरा करने या 'वापस जाएं' पर जाने की अनुमति दें
- टास्क पूरा होने पर, इतिहास की एंट्री हटाना
यह सुविधा, कुछ समय के लिए दिखने वाले मॉडल या इंटरस्टीशियल के लिए बेहतरीन हो सकती है: नया यूआरएल ऐसा होता है जिससे उपयोगकर्ता, बैक जेस्चर का इस्तेमाल करके बाहर निकल सकता है. हालांकि, वह इसे फिर से खोलने के लिए, गलती से फ़ॉरवर्ड नहीं जा सकता, क्योंकि एंट्री हटा दी गई है. मौजूदा History API की मदद से, ऐसा नहीं किया जा सकता.
Navigation API आज़माएं
Navigation API, Chrome 102 में बिना फ़्लैग के उपलब्ध है. डोमेनिक डेनिकोला का डेमो भी आज़माया जा सकता है.
क्लासिक History API आसान दिखता है, लेकिन इसे पूरी तरह से समझना मुश्किल है. साथ ही, इसमें कई समस्याएं हैं. इनमें, खास मामलों और अलग-अलग ब्राउज़र में इसे अलग-अलग तरीके से लागू करने के बारे में जानकारी शामिल है. हमें उम्मीद है कि आप नए Navigation API के बारे में सुझाव, शिकायत या राय देंगे.
रेफ़रंस
- WICG/navigation-api
- Mozilla के स्टैंडर्ड के बारे में जानकारी
- प्रोटोटाइप बनाने का मकसद
- टैग की समीक्षा
- Chromestatus एंट्री
आभार
इस पोस्ट की समीक्षा करने के लिए, थॉमस स्टाइनर, डोमेनिक डेनिकोला, और नेट चैपिन का धन्यवाद.