# টেরারিয়াম প্রকল্প পার্ট ৩: DOM ম্যানিপুলেশন এবং একটি ক্লোজার ![DOM এবং একটি ক্লোজার](../../../../translated_images/webdev101-js.10280393044d7eaaec7e847574946add7ddae6be2b2194567d848b61d849334a.bn.png) > স্কেচনোট: [Tomomi Imura](https://twitter.com/girlie_mac) ## প্রাক-লেকচার কুইজ [প্রাক-লেকচার কুইজ](https://ff-quizzes.netlify.app/web/quiz/19) ### ভূমিকা DOM বা "ডকুমেন্ট অবজেক্ট মডেল" ম্যানিপুলেশন ওয়েব ডেভেলপমেন্টের একটি গুরুত্বপূর্ণ দিক। [MDN](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) অনুসারে, "ডকুমেন্ট অবজেক্ট মডেল (DOM) হলো ওয়েবের একটি ডকুমেন্টের কাঠামো এবং বিষয়বস্তুর প্রতিনিধিত্বকারী ডেটা।" ওয়েবে DOM ম্যানিপুলেশন সংক্রান্ত চ্যালেঞ্জগুলো প্রায়ই জাভাস্ক্রিপ্ট ফ্রেমওয়ার্ক ব্যবহারের কারণ হয়ে দাঁড়ায়, তবে আমরা এখানে ভ্যানিলা জাভাস্ক্রিপ্ট ব্যবহার করেই এটি পরিচালনা করব! এছাড়াও, এই পাঠে [জাভাস্ক্রিপ্ট ক্লোজার](https://developer.mozilla.org/docs/Web/JavaScript/Closures) ধারণাটি পরিচিত করানো হবে। এটি এমন একটি ফাংশন যা আরেকটি ফাংশনের মধ্যে আবদ্ধ থাকে, ফলে অভ্যন্তরীণ ফাংশনটি বাইরের ফাংশনের স্কোপে অ্যাক্সেস পায়। > জাভাস্ক্রিপ্ট ক্লোজার একটি বিস্তৃত এবং জটিল বিষয়। এই পাঠে আমরা এর মৌলিক ধারণা নিয়ে আলোচনা করব। টেরারিয়ামের কোডে আপনি একটি ক্লোজার দেখতে পাবেন: একটি অভ্যন্তরীণ ফাংশন এবং একটি বাইরের ফাংশন এমনভাবে তৈরি করা হয়েছে যাতে অভ্যন্তরীণ ফাংশনটি বাইরের ফাংশনের স্কোপে অ্যাক্সেস পায়। এই বিষয়ে আরও বিস্তারিত জানতে [বিস্তৃত ডকুমেন্টেশন](https://developer.mozilla.org/docs/Web/JavaScript/Closures) দেখুন। আমরা DOM ম্যানিপুলেশনের জন্য একটি ক্লোজার ব্যবহার করব। DOM-কে একটি গাছের মতো ভাবুন, যা একটি ওয়েব পেজ ডকুমেন্টকে ম্যানিপুলেট করার সমস্ত উপায় উপস্থাপন করে। বিভিন্ন API (অ্যাপ্লিকেশন প্রোগ্রাম ইন্টারফেস) তৈরি করা হয়েছে যাতে প্রোগ্রামাররা তাদের পছন্দের প্রোগ্রামিং ভাষা ব্যবহার করে DOM-এ অ্যাক্সেস করতে এবং এটি সম্পাদনা, পরিবর্তন, পুনর্বিন্যাস এবং পরিচালনা করতে পারে। ![DOM গাছের উপস্থাপনা](../../../../translated_images/dom-tree.7daf0e763cbbba9273f9a66fe04c98276d7d23932309b195cb273a9cf1819b42.bn.png) > DOM এবং এর সাথে সম্পর্কিত HTML মার্কআপের একটি উপস্থাপনা। [Olfa Nasraoui](https://www.researchgate.net/publication/221417012_Profile-Based_Focused_Crawler_for_Social_Media-Sharing_Websites) থেকে নেওয়া। এই পাঠে, আমরা আমাদের ইন্টারেক্টিভ টেরারিয়াম প্রকল্পটি সম্পন্ন করব, যেখানে ব্যবহারকারী পৃষ্ঠার গাছপালাগুলো ম্যানিপুলেট করতে পারবে। ### প্রয়োজনীয়তা আপনার টেরারিয়ামের HTML এবং CSS তৈরি করা থাকা উচিত। এই পাঠের শেষে আপনি গাছপালাগুলো টেরারিয়ামে টেনে নিয়ে যেতে এবং সেখান থেকে সরাতে সক্ষম হবেন। ### কাজ আপনার টেরারিয়াম ফোল্ডারে একটি নতুন ফাইল তৈরি করুন যার নাম `script.js`। এই ফাইলটি `` সেকশনে ইমপোর্ট করুন: ```html ``` > নোট: একটি এক্সটার্নাল জাভাস্ক্রিপ্ট ফাইল HTML ফাইলে ইমপোর্ট করার সময় `defer` ব্যবহার করুন যাতে জাভাস্ক্রিপ্টটি HTML ফাইলটি পুরোপুরি লোড হওয়ার পরেই কার্যকর হয়। আপনি `async` অ্যাট্রিবিউটও ব্যবহার করতে পারেন, যা স্ক্রিপ্টটিকে HTML ফাইলটি পার্স করার সময় কার্যকর হতে দেয়, তবে আমাদের ক্ষেত্রে, ড্র্যাগ স্ক্রিপ্ট কার্যকর হওয়ার আগে HTML উপাদানগুলো সম্পূর্ণ উপলব্ধ থাকা গুরুত্বপূর্ণ। --- ## DOM উপাদানগুলো প্রথমে আপনাকে DOM-এ ম্যানিপুলেট করতে চান এমন উপাদানগুলোর রেফারেন্স তৈরি করতে হবে। আমাদের ক্ষেত্রে, এগুলো হলো সাইডবারে থাকা ১৪টি গাছপালা। ### কাজ ```html dragElement(document.getElementById('plant1')); dragElement(document.getElementById('plant2')); dragElement(document.getElementById('plant3')); dragElement(document.getElementById('plant4')); dragElement(document.getElementById('plant5')); dragElement(document.getElementById('plant6')); dragElement(document.getElementById('plant7')); dragElement(document.getElementById('plant8')); dragElement(document.getElementById('plant9')); dragElement(document.getElementById('plant10')); dragElement(document.getElementById('plant11')); dragElement(document.getElementById('plant12')); dragElement(document.getElementById('plant13')); dragElement(document.getElementById('plant14')); ``` এখানে কী হচ্ছে? আপনি ডকুমেন্টটি রেফারেন্স করছেন এবং এর DOM-এর মধ্যে একটি নির্দিষ্ট Id সহ একটি উপাদান খুঁজছেন। মনে আছে প্রথম HTML পাঠে আপনি প্রতিটি গাছপালার ছবিতে আলাদা Id দিয়েছিলেন (`id="plant1"`)। এখন আপনি সেই প্রচেষ্টার ফলাফল ব্যবহার করবেন। প্রতিটি উপাদান চিহ্নিত করার পর, আপনি সেই আইটেমটি একটি `dragElement` নামক ফাংশনে পাঠাচ্ছেন, যা আপনি কিছুক্ষণের মধ্যে তৈরি করবেন। এর ফলে HTML-এর উপাদানটি এখন ড্র্যাগ-সক্ষম হয়ে যাবে, বা শীঘ্রই হবে। ✅ আমরা কেন Id দিয়ে উপাদানগুলো রেফারেন্স করি? CSS ক্লাস দিয়ে কেন নয়? এই প্রশ্নের উত্তর পেতে আপনি CSS-এর পূর্ববর্তী পাঠটি দেখতে পারেন। --- ## ক্লোজার এখন আপনি `dragElement` ক্লোজার তৈরি করতে প্রস্তুত, যা একটি বাইরের ফাংশন যা একটি বা একাধিক অভ্যন্তরীণ ফাংশনকে আবদ্ধ করে (আমাদের ক্ষেত্রে, তিনটি থাকবে)। ক্লোজার তখনই কার্যকর হয় যখন এক বা একাধিক ফাংশনকে বাইরের ফাংশনের স্কোপে অ্যাক্সেস করতে হয়। একটি উদাহরণ দেখুন: ```javascript function displayCandy(){ let candy = ['jellybeans']; function addCandy(candyType) { candy.push(candyType) } addCandy('gumdrops'); } displayCandy(); console.log(candy) ``` এই উদাহরণে, `displayCandy` ফাংশনটি এমন একটি ফাংশনকে ঘিরে রাখে যা একটি নতুন ক্যান্ডি টাইপকে একটি অ্যারের মধ্যে ঠেলে দেয় যা ইতিমধ্যে ফাংশনে বিদ্যমান। আপনি যদি এই কোডটি চালান, তবে `candy` অ্যারে অপরিবর্তিত থাকবে, কারণ এটি একটি লোকাল ভেরিয়েবল (ক্লোজারের জন্য লোকাল)। ✅ কীভাবে আপনি `candy` অ্যারেটিকে অ্যাক্সেসযোগ্য করতে পারেন? এটি ক্লোজারের বাইরে সরিয়ে দেখুন। এভাবে, অ্যারেটি গ্লোবাল হয়ে যাবে, এবং ক্লোজারের লোকাল স্কোপে সীমাবদ্ধ থাকবে না। ### কাজ `script.js`-এ উপাদান ঘোষণাগুলোর নিচে একটি ফাংশন তৈরি করুন: ```javascript function dragElement(terrariumElement) { //set 4 positions for positioning on the screen let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; terrariumElement.onpointerdown = pointerDrag; } ``` `dragElement` তার `terrariumElement` অবজেক্টটি স্ক্রিপ্টের শীর্ষে থাকা ঘোষণাগুলো থেকে পায়। এরপর, আপনি কিছু লোকাল পজিশনকে `0` এ সেট করেন সেই অবজেক্টের জন্য যা ফাংশনে পাস করা হয়েছে। এগুলো হলো লোকাল ভেরিয়েবল, যা প্রতিটি উপাদানের জন্য ম্যানিপুলেট করা হবে যখন আপনি ক্লোজারের মধ্যে ড্র্যাগ এবং ড্রপ কার্যকারিতা যোগ করবেন। টেরারিয়ামটি এই ড্র্যাগ করা উপাদানগুলো দ্বারা পূর্ণ হবে, তাই অ্যাপ্লিকেশনটি তাদের অবস্থান ট্র্যাক করতে হবে। এছাড়াও, `terrariumElement` যা এই ফাংশনে পাস করা হয়েছে, সেটি একটি `pointerdown` ইভেন্টে অ্যাসাইন করা হয়েছে, যা [ওয়েব API](https://developer.mozilla.org/docs/Web/API)-এর অংশ যা DOM ম্যানেজমেন্টে সহায়ক। `onpointerdown` তখনই ফায়ার হয় যখন একটি বোতাম চাপা হয়, বা আমাদের ক্ষেত্রে, একটি ড্র্যাগযোগ্য উপাদান স্পর্শ করা হয়। এই ইভেন্ট হ্যান্ডলার [ওয়েব এবং মোবাইল ব্রাউজার](https://caniuse.com/?search=onpointerdown)-এ কাজ করে, কিছু ব্যতিক্রম ছাড়া। ✅ [ইভেন্ট হ্যান্ডলার `onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) অনেক বেশি ব্রাউজার সমর্থন পায়; এখানে কেন এটি ব্যবহার করবেন না? আপনি এখানে তৈরি করতে চাওয়া সুনির্দিষ্ট স্ক্রিন ইন্টারঅ্যাকশনের কথা ভাবুন। --- ## পয়েন্টারড্র্যাগ ফাংশন `terrariumElement` এখন ড্র্যাগ করার জন্য প্রস্তুত; যখন `onpointerdown` ইভেন্টটি ফায়ার হয়, তখন `pointerDrag` ফাংশনটি আহ্বান করা হয়। এই লাইনটির ঠিক নিচে ফাংশনটি যোগ করুন: `terrariumElement.onpointerdown = pointerDrag;`: ### কাজ ```javascript function pointerDrag(e) { e.preventDefault(); console.log(e); pos3 = e.clientX; pos4 = e.clientY; } ``` এখানে অনেক কিছু ঘটে। প্রথমে, আপনি `e.preventDefault();` ব্যবহার করে `pointerdown`-এ সাধারণত ঘটে এমন ডিফল্ট ইভেন্টগুলোকে প্রতিরোধ করেন। এভাবে আপনি ইন্টারফেসের আচরণের উপর আরও নিয়ন্ত্রণ পান। > পুরো স্ক্রিপ্ট ফাইলটি তৈরি করার পর এই লাইনটি বাদ দিয়ে দেখুন কী হয়? দ্বিতীয়ত, `index.html` একটি ব্রাউজার উইন্ডোতে খুলুন এবং ইন্টারফেসটি পরিদর্শন করুন। যখন আপনি একটি গাছপালা ক্লিক করেন, তখন আপনি দেখতে পাবেন কীভাবে 'e' ইভেন্টটি ক্যাপচার করা হয়। ইভেন্টটি বিশ্লেষণ করে দেখুন কত তথ্য একটি `pointerdown` ইভেন্ট দ্বারা সংগ্রহ করা হয়! পরবর্তী ধাপে, লক্ষ্য করুন কীভাবে লোকাল ভেরিয়েবল `pos3` এবং `pos4`-কে `e.clientX`-এ সেট করা হয়েছে। আপনি ইন্সপেকশন প্যানেলে `e` মানগুলো খুঁজে পেতে পারেন। এই মানগুলো গাছপালার x এবং y কোঅর্ডিনেটগুলো ক্যাপচার করে যখন আপনি এটি ক্লিক বা স্পর্শ করেন। আপনি গাছপালাগুলোর আচরণের উপর সূক্ষ্ম নিয়ন্ত্রণ রাখতে চান, তাই তাদের কোঅর্ডিনেটগুলো ট্র্যাক করেন। ✅ এটি কি আরও পরিষ্কার হচ্ছে কেন পুরো অ্যাপটি একটি বড় ক্লোজার দিয়ে তৈরি করা হয়েছে? যদি তা না হতো, তাহলে কীভাবে আপনি ১৪টি ড্র্যাগযোগ্য গাছপালার প্রতিটির স্কোপ বজায় রাখতেন? `pos4 = e.clientY`-এর নিচে আরও দুটি পয়েন্টার ইভেন্ট ম্যানিপুলেশন যোগ করে প্রাথমিক ফাংশনটি সম্পূর্ণ করুন: ```html document.onpointermove = elementDrag; document.onpointerup = stopElementDrag; ``` এখন আপনি নির্দেশ করছেন যে আপনি গাছপালাটিকে পয়েন্টারের সাথে টেনে নিয়ে যেতে চান এবং গাছপালাটি ছেড়ে দিলে ড্র্যাগিং অঙ্গভঙ্গি থেমে যাবে। `onpointermove` এবং `onpointerup` একই API-এর অংশ যা `onpointerdown`। ইন্টারফেস এখন ত্রুটি নিক্ষেপ করবে কারণ আপনি এখনও `elementDrag` এবং `stopElementDrag` ফাংশনগুলো সংজ্ঞায়িত করেননি, তাই এগুলো পরবর্তী ধাপে তৈরি করুন। ## `elementDrag` এবং `stopElementDrag` ফাংশন আপনি দুটি অভ্যন্তরীণ ফাংশন যোগ করে আপনার ক্লোজার সম্পূর্ণ করবেন যা গাছপালা ড্র্যাগ করার সময় এবং ড্র্যাগ বন্ধ করার সময় কী হবে তা পরিচালনা করবে। আপনি এমন একটি আচরণ চান যেখানে আপনি যেকোনো সময় যেকোনো গাছপালা টেনে নিয়ে যেতে পারেন এবং এটি স্ক্রিনের যেকোনো জায়গায় রাখতে পারেন। এই ইন্টারফেসটি বেশ নমনীয় (উদাহরণস্বরূপ, এখানে কোনো ড্রপ জোন নেই) যাতে আপনি আপনার টেরারিয়ামটি নিজের মতো করে ডিজাইন করতে পারেন। ### কাজ `pointerDrag`-এর বন্ধনী বন্ধ হওয়ার ঠিক পরে `elementDrag` ফাংশনটি যোগ করুন: ```javascript function elementDrag(e) { pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; console.log(pos1, pos2, pos3, pos4); terrariumElement.style.top = terrariumElement.offsetTop - pos2 + 'px'; terrariumElement.style.left = terrariumElement.offsetLeft - pos1 + 'px'; } ``` এই ফাংশনে, আপনি প্রাথমিক অবস্থান ১-৪-কে অনেক সম্পাদনা করেন যা আপনি বাইরের ফাংশনে লোকাল ভেরিয়েবল হিসেবে সেট করেছিলেন। এখানে কী হচ্ছে? যখন আপনি ড্র্যাগ করেন, তখন আপনি `pos1`-কে পুনরায় অ্যাসাইন করেন, এটি `pos3` (যা আপনি আগে `e.clientX` হিসেবে সেট করেছিলেন) এবং বর্তমান `e.clientX` মানের মধ্যে পার্থক্যের সমান করে। আপনি `pos2`-এর জন্য একই অপারেশন করেন। এরপর, আপনি `pos3` এবং `pos4`-কে উপাদানের নতুন X এবং Y কোঅর্ডিনেটে রিসেট করেন। আপনি ড্র্যাগ করার সময় কনসোলে এই পরিবর্তনগুলো দেখতে পারেন। এরপর, আপনি গাছপালার CSS স্টাইল ম্যানিপুলেট করেন যাতে এর নতুন অবস্থান সেট করা যায়, যা `pos1` এবং `pos2`-এর নতুন অবস্থানের উপর ভিত্তি করে গণনা করা হয়, এর অফসেটের সাথে তুলনা করে। > `offsetTop` এবং `offsetLeft` হলো CSS প্রোপার্টি যা একটি উপাদানের অবস্থান সেট করে তার প্যারেন্টের উপর ভিত্তি করে; এর প্যারেন্ট যেকোনো উপাদান হতে পারে যা `static` হিসেবে পজিশন করা হয়নি। এই অবস্থান পুনর্গণনা টেরারিয়াম এবং এর গাছপালাগুলোর আচরণ সূক্ষ্মভাবে টিউন করতে দেয়। ### কাজ ইন্টারফেসটি সম্পূর্ণ করতে চূড়ান্ত কাজ হলো `stopElementDrag` ফাংশনটি `elementDrag`-এর বন্ধনী বন্ধ হওয়ার পরে যোগ করা: ```javascript function stopElementDrag() { document.onpointerup = null; document.onpointermove = null; } ``` এই ছোট ফাংশনটি `onpointerup` এবং `onpointermove` ইভেন্টগুলো রিসেট করে যাতে আপনি আবার গাছপালার অগ্রগতি পুনরায় শুরু করতে পারেন বা একটি নতুন গাছপালা ড্র্যাগ করতে পারেন। ✅ যদি আপনি এই ইভেন্টগুলো null-এ সেট না করেন তবে কী হয়? এখন আপনি আপনার প্রকল্পটি সম্পন্ন করেছেন! 🥇অভিনন্দন! আপনি আপনার সুন্দর টেরারিয়ামটি শেষ করেছেন। ![শেষ টেরারিয়াম](../../../../translated_images/terrarium-final.0920f16e87c13a84cd2b553a5af9a3ad1cffbd41fbf8ce715d9e9c43809a5e2c.bn.png) --- ## 🚀চ্যালেঞ্জ আপনার ক্লোজারে একটি নতুন ইভেন্ট হ্যান্ডলার যোগ করুন যা গাছপালাগুলোর জন্য আরও কিছু করে; উদাহরণস্বরূপ, একটি গাছপালাকে সামনে আনতে ডাবল-ক্লিক করুন। সৃজনশীল হন! ## পোস্ট-লেকচার কুইজ [পোস্ট-লেকচার কুইজ](https://ff-quizzes.netlify.app/web/quiz/20) ## পর্যালোচনা ও স্ব-অধ্যয়ন স্ক্রিনে উপাদানগুলো ড্র্যাগ করা তুচ্ছ মনে হতে পারে, তবে এটি করার অনেক উপায় এবং অনেক ফাঁদ রয়েছে, যা আপনি যে প্রভাবটি চান তার উপর নির্ভর করে। প্রকৃতপক্ষে, একটি সম্পূর্ণ [ড্র্যাগ এবং ড্রপ API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API) রয়েছে যা আপনি চেষ্টা করতে পারেন। আমরা এই মডিউলে এটি ব্যবহার করিনি কারণ আমরা একটি ভিন্ন প্রভাব চেয়েছিলাম, তবে এই API আপনার নিজস্ব প্রকল্পে চেষ্টা করুন এবং দেখুন আপনি কী অর্জন করতে পারেন। পয়েন্টার ইভেন্ট সম্পর্কে আরও তথ্য খুঁজুন [W3C ডকস](https://www.w3.org/TR/pointerevents1/) এবং [MDN ওয়েব ডকস](https://developer.mozilla.org/docs/Web/API/Pointer_events)-এ। সবসময় ব্রাউজার সক্ষমতা পরীক্ষা করুন [CanIUse.com](https://caniuse.com/) ব্যবহার করে। ## অ্যাসাইনমেন্ট [DOM নিয়ে আরও কাজ করুন](assignment.md) **অস্বীকৃতি**: এই নথিটি AI অনুবাদ পরিষেবা [Co-op Translator](https://github.com/Azure/co-op-translator) ব্যবহার করে অনুবাদ করা হয়েছে। আমরা যথাসম্ভব সঠিকতার জন্য চেষ্টা করি, তবে অনুগ্রহ করে মনে রাখবেন যে স্বয়ংক্রিয় অনুবাদে ত্রুটি বা অসঙ্গতি থাকতে পারে। মূল ভাষায় থাকা নথিটিকে প্রামাণিক উৎস হিসেবে বিবেচনা করা উচিত। গুরুত্বপূর্ণ তথ্যের জন্য, পেশাদার মানব অনুবাদ সুপারিশ করা হয়। এই অনুবাদ ব্যবহারের ফলে কোনো ভুল বোঝাবুঝি বা ভুল ব্যাখ্যা হলে আমরা তার জন্য দায়ী থাকব না।