पृष्ठ विभाजन (Pagination)
पृष्ठ विभाजन (Pagination) एक धोखेबाज़ी से जटिल विषय हो सकता है। जाल में फँसना और सर्वोत्तम प्रथाओं का पालन न करना आसान है। यह पृष्ठ आपको पृष्ठ विभाजन "सही" तरीके से करने में मदद करेगा। यानी, यदि आप इस पृष्ठ को पढ़ते और समझते हैं, तो हम सोचते हैं कि आपका क्लाइंट अधिक मजबूत और भविष्य के लिए सुरक्षित होगा और आगे चलकर आपका काम आसान कर देगा।
यदि आप इस गाइड से केवल एक चीज़ ले जाते हैं, तो यह होना चाहिए कि आपको अपने स्वयं के pagination URLs का निर्माण नहीं करना चाहिए।
JSON:API मॉड्यूल से प्रत्येक पेजिनेटेड प्रतिक्रिया में पहले से ही संग्रह (collection) के अगले पृष्ठ के लिए लिंक होता है जिसका आप उपयोग कर सकते हैं। आपको उसी लिंक का पालन करना चाहिए।
इस दस्तावेज़ की शुरुआत में, हम API की कुछ महत्वपूर्ण विशेषताओं और पृष्ठ विभाजन "सही" तरीके से लागू करने के बारे में देखेंगे। इस दस्तावेज़ के अंत में, आपको सामान्य प्रश्नों और समस्याओं के उत्तर मिलेंगे।
कैसे?
JSON:API मॉड्यूल से प्रत्येक पेजिनेटेड प्रतिक्रिया में पृष्ठ विभाजन लिंक बने हुए आते हैं। आइए एक छोटा उदाहरण देखें:
{
"data": [
{"type": "sample--type", "id": "abcd-uuid-here"},
{"type": "sample--type", "id": "efgh-uuid-here"}
],
"links": {
"self": "<collection_url>?page[offset]=3&page[limit]=3",
"next": "<collection_url>?page[offset]=6&page[limit]=3",
"prev": "<collection_url>?page[offset]=0&page[limit]=3"
}
}
कुछ बातों पर ध्यान दें:
links
कुंजी के अंतर्गत 3 pagination लिंक हैं:self
: यह वर्तमान पृष्ठ का URL है।next
: यह अगले पृष्ठ का URL है।prev
: यह पिछले पृष्ठ का URL है।
page[limit]
3 का है, लेकिन केवल 2 संसाधन हैं (?!)
पृष्ठ विभाजन लिंक की उपस्थिति या अनुपस्थिति महत्वपूर्ण है। आपको जानने की आवश्यकता है:
- यदि
next
लिंक मौजूद है, तो और पृष्ठ हैं। - यदि
next
लिंक मौजूद नहीं है, तो आप अंतिम पृष्ठ पर हैं। - यदि
prev
लिंक मौजूद है, तो आप पहले पृष्ठ पर नहीं हैं। - यदि न तो
next
और न हीprev
लिंक मौजूद है, तो केवल एक पृष्ठ है।
हालाँकि यहाँ 3 का पृष्ठ सीमा (page limit) है, केवल 2 संसाधन हैं! यह इसलिए है क्योंकि सुरक्षा कारणों से एक entity हटा दी गई थी। हम बता सकते हैं कि यह इसलिए नहीं है क्योंकि प्रतिक्रिया भरने के लिए पर्याप्त संसाधन नहीं हैं, क्योंकि हम देख सकते हैं कि एक next
लिंक है। यदि आप इसके बारे में अधिक जानना चाहते हैं, तो यह नीचे अधिक विस्तार से समझाया गया है।
ठीक है, अब जब हमने कुछ महत्वपूर्ण तथ्य स्थापित कर लिए हैं। आइए सोचें कि हमें अपना क्लाइंट कैसे बनाना चाहिए। हम मदद के लिए कुछ pseudo-JavaScript देखेंगे। 🧐
मान लें कि आप हमारी साइट पर नवीनतम सामग्री की सूची दिखाना चाहते हैं और हमारे पास कुछ "प्रीमियम" सामग्री है। केवल भुगतान करने वाले सब्सक्राइबर को प्रीमियम सामग्री देखने की अनुमति दी जानी चाहिए। हमने यह भी तय किया है कि हम एक "शीर्ष 5" घटक (component) चाहते हैं, हालाँकि, यदि अधिक सामग्री मौजूद है, तो उपयोगकर्ता को "अगला पृष्ठ" लिंक पर क्लिक करके अगले 5 नवीनतम सामग्री टुकड़े देखने में सक्षम होना चाहिए।
एक साधारण (naive) इम्प्लीमेंटेशन कुछ इस तरह दिख सकता है:
const baseUrl = 'http://example.com';
const path = '/jsonapi/node/content';
const pager = 'page[limit]=5';
const filter = `filter[field_premium][value]=${user.isSubscriber()}`;
fetch(`${baseUrl}${path}?${pager}&${filter}`)
.then(resp => {
return resp.ok ? resp.json() : Promise.reject(resp.statusText);
})
.then(document => listComponent.setContent(document.data))
.catch(console.log);
हालाँकि, खराब error handling को नज़रअंदाज करने पर भी, हम पहले से जानते हैं कि यह एक बहुत मजबूत इम्प्लीमेंटेशन नहीं है।
हमने ऊपर देखा कि हम यह सुनिश्चित नहीं कर सकते कि किसी प्रतिक्रिया में 5 आइटम होंगे। यदि उन entities में से 2 सुलभ नहीं हैं (शायद वे unpublished हैं) तो हमारे "शीर्ष 5" घटक में केवल 3 आइटम होंगे!
हमारे पास एक अनावश्यक फ़िल्टर भी है। सर्वर को पहले से ही उस सामग्री को हटाना चाहिए जिसे उपयोगकर्ता देखने की अनुमति नहीं है। यदि नहीं, तो हमारे एप्लिकेशन में संभावित access bypass होगा क्योंकि कोई दुर्भावनापूर्ण उपयोगकर्ता आसानी से क्वेरी को बदलकर "प्रीमियम" सामग्री देख सकता है। हमेशा सुनिश्चित करें कि आप सर्वर पर एक्सेस नियंत्रण लागू करें; अपनी क्वेरी पर भरोसा न करें।
आइए इसे ठीक करें:
const listQuota = 5;
const content = [];
const baseUrl = 'http://example.com';
const path = '/jsonapi/node/content';
const pager = `page[limit]=${listQuota}`;
const getAndSetContent = (link) => {
fetch(link)
.then(resp => {
return resp.ok ? resp.json() : Promise.reject(resp.statusText);
})
.then(document => {
content.push(...document.data);
listContent.setContent(content.slice(0, listQuota));
const hasNextPage = document.links.hasOwnProperty("next");
if (content.length <= listQuota && hasNextPage) {
getAndSetContent(document.links.next);
}
if (content.length > listQuota || hasNextPage) {
const nextPageLink = hasNextPage
? document.links.next
: null;
listComponent.showNextPageLink(nextPageLink);
}
})
.catch(console.log);
}
getAndSetContent(`${baseUrl}${path}?${pager}`)
सबसे पहले, आप देख सकते हैं कि filter
गायब है। ऐसा इसलिए है क्योंकि हम मान रहे हैं कि एक्सेस चेक सर्वर पर किए जा रहे हैं, न कि फ़िल्टर पर निर्भर होकर। यह एकमात्र सुरक्षित समाधान है। हम इसे प्रदर्शन अनुकूलन के रूप में वापस जोड़ सकते हैं, लेकिन इसकी शायद ज़रूरत नहीं है।
इसके बाद, चूंकि हम जानते हैं कि सर्वर बस उन संसाधनों को हटा देता है जो उपयोगकर्ता के लिए सुलभ नहीं हैं, हमें वास्तव में जाँच करनी चाहिए कि प्रतिक्रिया में कितने संसाधन वास्तव में हैं।
"naive" इम्प्लीमेंटेशन में, हम मान रहे थे कि प्रत्येक प्रतिक्रिया में 5 आइटम होंगे। इस उदाहरण में, अब हम 5 संसाधनों का "quota" सेट करते हैं। अपने अनुरोध करने के बाद, हम यह देखने के लिए जाँचते हैं कि हमने अपना quota पूरा किया है या नहीं। हम यह भी जाँचते हैं कि सर्वर के पास अभी भी और पृष्ठ हैं या नहीं (हमें यह इसलिए पता चलेगा क्योंकि इसमें next
लिंक होगा, याद है?).
यदि हमने quota पूरा नहीं किया है और हम अंतिम पृष्ठ पर नहीं हैं, तो हम दस्तावेज़ से निकाले गए next
लिंक का उपयोग करके एक और अनुरोध करते हैं। यह ध्यान देने योग्य है कि हमने अगले पृष्ठ के लिए नया URL मैन्युअल रूप से निर्मित नहीं किया। ऐसा करने की कोई आवश्यकता नहीं है क्योंकि JSON:API सर्वर ने पहले से ही यह हमारे लिए कर दिया है!
एक और दिलचस्प बात यह है कि चूंकि fetch
असिंक्रोनस है, हम अपने घटक में पहले अनुरोध की सामग्री जोड़ सकते हैं, भले ही सभी अनुरोध पूरे न हुए हों। जब दूसरा अनुरोध पूरा होता है, तो हम बस घटक को फिर से अपडेट करते हैं ताकि उसमें नए प्राप्त परिणाम शामिल हों।
अंत में, हम सुनिश्चित करते हैं कि हमारा काल्पनिक listComponent
यह जानता है कि "अगला पृष्ठ" लिंक दिखाना है या नहीं। इसे केवल तभी लिंक दिखाना चाहिए जब हमारे पास पहले से अतिरिक्त सामग्री हो या यदि सर्वर के पास अतिरिक्त पृष्ठ हों।
पहले मामले में ऐसा हो सकता है कि हमें पहले अनुरोध में केवल 4 आइटम प्राप्त हों और दूसरे में 5 आइटम लेकिन कोई next
लिंक न हो। उस स्थिति में, हमारे पास कुल 9 आइटम होंगे लेकिन हमारा listComponent
केवल पहले 5 दिखाएगा। इसलिए हम अभी भी घटक पर "अगला पृष्ठ" लिंक दिखाना चाहते हैं लेकिन हम नहीं चाहते कि हमारा घटक वास्तव में और अनुरोध भेजे। इसे इंगित करने के लिए, हम nextPageLink
को null
पर सेट करते हैं।
दूसरे मामले में—जब हमारे पास next
लिंक होता है—हम उस अगले पृष्ठ लिंक को अपने घटक को पास करते हैं ताकि वह इसका उपयोग करके एक बाद का अनुरोध कर सके। हम वह अनुरोध नहीं करना चाहते यदि उपयोगकर्ता ने कभी "अगला पृष्ठ" लिंक पर क्लिक नहीं किया।
अंतिम कुछ अनुच्छेद एक वास्तव में महत्वपूर्ण अवधारणा को दर्शाते हैं... आपके HTML में "अगला पृष्ठ" लिंक का API पृष्ठों से संबंधित होना आवश्यक नहीं है! वास्तव में, यदि वे संबंधित हैं, तो यह इंगित करता है कि आप इसे "गलत" कर रहे हो सकते हैं।
क्यों ... ?
... मैं पृष्ठ सीमा (page limit) 50 से अधिक क्यों नहीं सेट कर सकता?
सबसे पहले, ऊपर दिया गया उदाहरण पढ़ें। समझें कि JSON:API प्रत्येक entity के लिए व्यक्तिगत एक्सेस चेक चलाना चाहिए। दूसरे, समझें कि JSON:API मॉड्यूल का उद्देश्य "zero configuration" होना है। आपको मॉड्यूल का उपयोग करने के लिए कुछ भी इंस्टॉल, परिवर्तित या कॉन्फ़िगर नहीं करना चाहिए।
इसका कारण आपकी एप्लिकेशन को DDoS हमले से बचाना है। यदि कोई दुर्भावनापूर्ण API क्लाइंट 200,000 संसाधनों की पृष्ठ सीमा सेट करता है, तो JSON:API मॉड्यूल को उन सभी entities के लिए entity access चेक चलाने की आवश्यकता होगी। इससे जल्दी ही out-of-memory त्रुटियाँ और धीमी प्रतिक्रियाएँ मिलेंगी। सर्वर को एक अधिकतम सीमा सेट करने की आवश्यकता है। 50 की सीमा को आंशिक रूप से एक अच्छा गोल अंक मानकर चुना गया था।
कृपया समझें कि इस निर्णय के आसपास कई लंबी बातचीत हुई हैं और समर्थन बोझ, उचित डिफ़ॉल्ट्स, और frontend प्रदर्शन के बीच एक समझौता करना पड़ा। जबकि JSON:API मॉड्यूल मेंटेनर समझते हैं कि यह हर उपयोग मामले के लिए आदर्श नहीं हो सकता, वे आश्वस्त हैं कि यदि आपका क्लाइंट इन डॉक्स में दिए गए अनुशंसाओं का पालन करता है, तो इसका आप पर बहुत कम या कोई प्रभाव नहीं होना चाहिए :)
यदि आप अभी भी अधिक सीमा चाहते हैं, तो आप JSON:API Page Limit मॉड्यूल का उपयोग कर सकते हैं।
... क्या प्रतिक्रिया में X संख्या के संसाधन नहीं हैं?
JSON:API मॉड्यूल आपको पृष्ठ सीमा निर्दिष्ट करने देता है, इसे अक्सर इस गारंटी के रूप में गलत समझा जाता है कि प्रतिक्रिया में कुछ संख्या में संसाधन शामिल होंगे। उदाहरण के लिए, आप जानते होंगे कि "भरने" के लिए पर्याप्त संसाधन उपलब्ध हैं, लेकिन प्रतिक्रिया में आपके द्वारा अपेक्षित संसाधनों की संख्या नहीं है।
उपरोक्त कई कारणों के लिए, JSON:API केवल उतने ही आइटम्स के लिए डेटाबेस क्वेरी चलाता है जितना page[limit]
क्वेरी पैरामीटर द्वारा निर्दिष्ट किया गया है। यह केवल एक अधिकतम है। यदि क्वेरी परिणाम में कुछ संसाधनों तक पहुँच की अनुमति नहीं है, तो उन संसाधनों को प्रतिक्रिया से हटा दिया जाएगा। ऐसी स्थिति में, आपको अपेक्षा से कम संसाधन दिखाई देंगे।
यह काफी आम है जब उन entities के लिए अनुरोध किया जाता है जो अप्रकाशित (unpublished) हो सकती हैं (जैसे nodes) और उन entities को पहले से filter
क्वेरी पैरामीटर का उपयोग करके फ़िल्टर नहीं किया गया हो।
लेख स्रोत: Drupal Documentation।