हाय हमर! इस लेख में, मैं डेटा बाइंडिंग के संदर्भ में क्लाइंट-सर्वर वेब एप्लिकेशन आर्किटेक्चर के निर्माण के विकल्पों में से एक पर विचार करूंगा। यह विकल्प मूल होने का दिखावा नहीं करता है, लेकिन व्यक्तिगत रूप से, मैंने विकास के समय को कम करने की अनुमति दी है, साथ ही डाउनलोड समय का अनुकूलन भी किया है।
समस्या
मान लें कि हमारे पास एक बड़ा वेब इंटरफ़ेस है जो उपयोगकर्ता को एक ही समय में विभिन्न प्रकार के कई सौ तत्वों के साथ बातचीत करने की अनुमति देता है।
प्रत्येक प्रकार की वस्तु के लिए हमारे पास अपनी कक्षा है, और हम स्वाभाविक रूप से इन कक्षाओं के तरीकों के साथ उपयोगकर्ता क्रियाओं को इंटरफ़ेस में जोड़ना चाहते हैं।
इस मामले में, हम एक सरल उदाहरण पर विचार करेंगे - ऑब्जेक्ट का एक नाम (नाम संपत्ति) है, और ऑब्जेक्ट प्रबंधन इंटरफ़ेस एक टेक्स्ट फ़ील्ड है जहां यह नाम दर्ज किया गया है। फ़ील्ड बदलते समय, हम चाहते हैं कि ऑब्जेक्ट की संपत्ति बदल जाए, और नया मान सर्वर (सेटनाम विधि) को भेजा जाए।
आमतौर पर, एक विशिष्ट अनुप्रयोग आरंभीकरण अनुक्रम इस तरह दिखता है:
- सभी वस्तुओं को प्रारंभिक
- इंटरफ़ेस का एक DOM ट्री बनाएँ
- मुख्य इंटरफ़ेस तत्वों (ऑब्जेक्ट कंटेनर, ऑब्जेक्ट संपादन फ़ॉर्म, आदि) के लिंक प्राप्त करें
- ऑब्जेक्ट गुणों के वर्तमान मूल्यों के साथ इंटरफ़ेस को प्रारंभ करें
- इंटरफ़ेस तत्वों को इवेंट हैंडलर के रूप में ऑब्जेक्ट मेथड्स असाइन करें
माथा
एकल वस्तु के लिए सबसे सरल कार्यान्वयन इस प्रकार है:
function InitDomForObject(object){
इस कार्यान्वयन के स्पष्ट नुकसान हैं:
- सख्ती से संबंधित लेआउट और जेएस कोड
- DOM के निर्माण और ईवेंट हैंडलर्स (जटिल इंटरफ़ेस के लिए) के लिए कोड की एक बड़ी राशि
- इस दृष्टिकोण के साथ, DOM के निर्माण में ब्राउज़र में बहुत अधिक समय लगेगा, क्योंकि हम प्रत्येक ऑब्जेक्ट के लिए लूप में कई बार createElement, setAttribute और appendChild कहेंगे, और ये काफी "भारी" ऑपरेशन हैं
टेम्पलेट्स
इस तरह की कठिनाइयों का सामना करते हुए, यह तुरंत टेम्प्लेट का उपयोग करने और मैन्युअल रूप से DOM को उत्पन्न न करने के लिए हमारे साथ हुआ, क्योंकि हम अच्छी तरह से जानते हैं कि यदि हम HTML कोड के साथ एक स्ट्रिंग के रूप में एक इंटरफ़ेस का निर्माण करते हैं, तो यह लगभग तुरंत ब्राउज़र द्वारा संसाधित किया जाएगा।
हमने HTML बनाया (उदाहरण के लिए, सर्वर की तरफ), और इसे ब्राउज़र में आउटपुट किया, लेकिन इस तथ्य का सामना किया कि हमें एक बड़े DOM ट्री में तत्वों की तलाश करनी थी।
इस तथ्य के कारण कि हमें तत्वों को हैंडलर आवंटित करने की आवश्यकता है - हम आलसी आरंभीकरण का उपयोग नहीं कर सकते हैं, हमें अभी भी एप्लिकेशन लोड करते समय बिल्कुल सभी इंटरफ़ेस तत्वों को ढूंढना होगा।
मान लीजिए कि विभिन्न प्रकारों के हमारे सभी ऑब्जेक्ट्स में एंड-टू-एंड नंबरिंग है, और ऑब्जेक्ट्स की एक सरणी में एकत्र किए जाते हैं।
अब हमारे पास दो तरीके हैं:
विकल्प ए
रिले विशेषता में ऑब्जेक्ट आईडी लिखकर कक्षा द्वारा तत्वों की खोज करें।
किसी एकल ऑब्जेक्ट का HTML प्रतिनिधित्व इस तरह दिखाई देगा:
<div class="container" rel="12345"> <input type="text" class="objectName" rel="12345" /> </div>
फिर, प्रत्येक प्रकार के इंटरफ़ेस तत्व के लिए, हम इस हैंडलर की तरह कुछ असाइन करते हैं:
$(".objectName").change(function(){ var id = $(this).attr("rel");
और अगर किसी कारण से हम प्रत्येक इंटरफ़ेस तत्व के लिंक को व्यक्तिगत रूप से सहेजना चाहते हैं, तो हमें आम तौर पर वस्तुओं के पूरे सरणी में एक डरावना चक्र लिखना होगा: यह लंबा और असुविधाजनक है।
विकल्प बी
बेशक, हम जानते हैं कि आईडी द्वारा तत्वों को ढूंढना बहुत तेज़ है!
चूँकि हमारी आईडी अद्वितीय होनी चाहिए, हम उदाहरण के लिए निम्न नाम "name_12345" ("role_identifier") का उपयोग कर सकते हैं:
<div id="container_12345" class="container"> <input type="text" id="name_12345" class="objectName" /> </div>
हैंडलर का काम लगभग एक जैसा दिखेगा:
$(".objectName").change(function(){ var id = this.id.split("_")[1];
चूंकि अब सभी तत्व आईडी द्वारा पाए जा सकते हैं, और हैंडलर पहले से ही असाइन किए गए हैं, हम अच्छी तरह से एक बार में सभी लिंक एकत्र नहीं कर सकते हैं, लेकिन इसे आवश्यक ("आलसी") करें, हमारे सभी ऑब्जेक्ट्स के बेस प्रोटोटाइप में कहीं न कहीं गेटेमेंट विधि को लागू करना:
function GetElement(element_name){ if(!this.element_cache[element_name]) this.element_cache[element_name] = document.getElementById(element_name + '_' + this.id); return this.element_cache[element_name]; }
आईडी द्वारा खोज करना पहले से ही बहुत तेज़ है, लेकिन कैश ने कभी किसी को परेशान नहीं किया है। हालांकि, यदि आप पेड़ से वस्तुओं को हटाने का इरादा रखते हैं, तो ध्यान रखें कि जब तक उनके लिए लिंक हैं, कचरा कलेक्टर उन्हें नहीं मिलेगा।
हमारे पास केवल एक समस्या होगी: ईवेंट हैंडलर्स के लिए बड़ी मात्रा में गंतव्य कोड, क्योंकि प्रत्येक प्रकार के इंटरफ़ेस तत्व के लिए हमें प्रत्येक ईवेंट के लिए एक अलग हैंडलर असाइन करना होगा! नियुक्तियों की कुल संख्या समान होगी =
वस्तुओं की संख्या X तत्वों की संख्या X घटनाओं की संख्या ।
अंतिम निर्णय
DOM में घटनाओं की उल्लेखनीय संपत्ति को याद करें: कैप्चर करना
और बुदबुदाना :
क्योंकि हम ईवेंट हैंडलर को रूट एलिमेंट में असाइन कर सकते हैं, क्योंकि सभी समान, सभी ईवेंट उसी से होकर गुजरते हैं!
हम इस उद्देश्य के लिए jQuery.live विधि का उपयोग कर सकते हैं, और ऊपर लिखी गई एक ही चीज़ के लिए आएगा:
विकल्प बी , अर्थात्, हैंडलर्स के लिए गंतव्य कोड की एक बड़ी संख्या।
इसके बजाय, हम अपनी घटनाओं के लिए एक छोटा "राउटर" लिखेंगे। हम तत्वों को बाहर करने के लिए एक विशेष चरित्र के साथ सभी आईडी तत्वों को शुरू करने के लिए सहमत हैं, जिसके लिए किसी भी घटना संचालकों की आवश्यकता नहीं है। राउटर उस ईवेंट को उस ऑब्जेक्ट पर पुनर्निर्देशित करने का प्रयास करेगा जिसके लिए यह तत्व "संबंधित" है और उस पर उपयुक्त विधि को कॉल करें।
var Router={ EventTypes : ['click', 'change', 'dblclick', 'mouseover', 'mouseout', 'dragover', 'keypress', 'keyup', 'focusout', 'focusin'],
उपयोग उदाहरण:
<input type="text" id="-Name_12345">
SomeObject.prototype = { … Name_blur : function(e){
समाधान के लाभ:
- कई अलग-अलग हैंडलर, न्यूनतम कोड निर्दिष्ट करने की आवश्यकता नहीं है
- प्रत्येक घटना को स्वचालित रूप से वांछित वस्तु विधि में रूट किया जाता है, और विधि को वांछित संदर्भ में कहा जाता है।
- आप किसी भी समय लेआउट से इंटरफ़ेस तत्वों को जोड़ / हटा सकते हैं, आपको केवल अपनी वस्तुओं के लिए उपयुक्त तरीकों को लागू करने की आवश्यकता है
- हैंडलर असाइनमेंट की संख्या घटना प्रकारों की संख्या के बराबर है (और वस्तुओं की संख्या एक्स तत्वों की संख्या एक्स घटनाओं की संख्या )
विपक्ष:
- आईडी तत्वों को एक विशेष तरीके से और बड़ी मात्रा में असाइन करना आवश्यक है। (यह केवल बदलते टेम्प्लेट की आवश्यकता है)
- प्रत्येक तत्व में प्रत्येक घटना के साथ, हमारा EventHandler कहा जाता है (यह लगभग प्रदर्शन को कम नहीं करता है, क्योंकि हम तुरंत अनावश्यक तत्वों को छोड़ देते हैं, और स्टॉपप्रोपेगैशन भी कहते हैं)
- वस्तुओं की संख्या अंत-से-अंत तक होनी चाहिए (आप ऑब्जेक्ट सरणी को कई में विभाजित कर सकते हैं - प्रत्येक प्रकार की वस्तुओं के लिए एक, या यहां तक कि सर्वर पर समान आईडी का उपयोग करने के बजाय अलग आंतरिक अनुक्रमणिका दर्ज करें)
अन्य विकल्प
जल्दी ठीक करो
यदि हमारा इंटरफ़ेस मानक और सरल था (यानी, केवल मानक नियंत्रण का उपयोग करके), हम सामान्य डेटा बाइंडिंग तकनीक को लागू करेंगे, उदाहरण के लिए jQuery DataLink:
$("#container").link(object, { name : "objectName" } );
जब किसी वस्तु की संपत्ति को बदलते हैं, तो पाठ क्षेत्र का मूल्य बदल जाता है और इसके विपरीत।
हालांकि, वास्तव में, हम अक्सर "मानक इंटरफ़ेस तत्व" से एक वस्तु की एक संपत्ति की तुलना में अधिक जटिल निर्भरता का उपयोग करते हैं। एक फ़ील्ड को बदलना कई गुणों को एक साथ, और विभिन्न वस्तुओं के लिए प्रभावित कर सकता है।
उदाहरण के लिए, यदि हमारे पास कुछ उपयोगकर्ता हैं जो कुछ अधिकारों के साथ (उपयोगकर्ता) है, जो एक समूह से संबंधित है, और एक ऐसा तत्व भी है जिसमें आप एक समूह (
GroupA या
GroupB ) का चयन कर सकते हैं।
फिर इस सूची में पसंद का बदलाव कई अन्य बदलाव करेगा:
डेटा में:
- UserA.group प्रॉपर्टी बदल जाएगी
- UserA ऑब्जेक्ट GroupA.users सरणी से हटा दिया जाएगा
- UserB ऑब्जेक्ट GroupB.users सरणी से हटा दिया जाएगा
- सरणी UserA.permissions बदल जाएगा
इंटरफ़ेस में:
- उपयोगकर्ता अधिकारों की सूची बदल जाएगी
- समूह में उपयोगकर्ताओं की संख्या दिखाने वाले काउंटर बदल जाएंगे।
आदि
इस तरह की जटिल निर्भरता को आसानी से हल नहीं किया जा सकता है। बस इस मामले में, ऊपर वर्णित विधि करेगी।
इसी तरह का समाधान
VKontakte में एक समान दृष्टिकोण लागू किया गया था: घटनाओं को प्रत्येक तत्व को इसी विशेषताओं (ऑनक्लिक, ऑनमॉस्वर, आदि) के माध्यम से सौंपा गया है। केवल टेम्पलेट क्लाइंट साइड पर बनाया गया है, न कि सर्वर पर।
हालाँकि, ईवेंट हैंडलिंग किसी भी ऑब्जेक्ट को नहीं सौंपा गया है:
<div class="dialogs_row" id="im_dialog78974230" onclick="IM.selectDialog(78974230); return false;">
इसके बजाय, वैश्विक वस्तुओं के तरीकों को कहा जाता है, जो बहुत अच्छा नहीं है यदि, उदाहरण के लिए, एप्लिकेशन में ओओपी दृष्टिकोण का उपयोग किया जाता है।
हम इस सिद्धांत को बदल सकते हैं, फिर भी घटनाओं को आवश्यक तरीकों से निर्देशित कर सकते हैं, लेकिन यह बहुत सुंदर नहीं लगेगा:
<div class="dialogs_row" id="im_dialog78974230" onclick="Dialog.prototype.select.call(Objects[78974230], event); return false;">
इसके बजाय, हम अपने राउटर फ़ंक्शन को इस दृष्टिकोण में अनुकूलित कर सकते हैं:
<input type="text" id="Name_12345" onblur="return Route(event);">
function Router(event){ var route = event.target.id.split('_'); var elementRole = route[0]; var objectId = route[1]; var object = App.Objects[objectId];
यह हमें प्रत्येक "छींक" उपयोगकर्ता के लिए बड़ी संख्या में घटनाओं को संसाधित करने से बचाएगा, लेकिन हमें उन घटनाओं का वर्णन करेगा जो उन्हें प्रत्येक तत्व के टेम्पलेट में चाहिए, और इनलाइन कोड के साथ भी।
इनमें से कौन सी बुराइयाँ सबसे छोटी हैं, और तदनुसार किस विधि को चुनना है, यह संदर्भ और विशिष्ट कार्य पर निर्भर करता है।