ছায়া DOM 101

ভূমিকা

ওয়েব কম্পোনেন্ট হল অত্যাধুনিক মানগুলির একটি সেট যা:

  1. উইজেট তৈরি করা সম্ভব করুন
  2. … যা নির্ভরযোগ্যভাবে পুনরায় ব্যবহার করা যেতে পারে
  3. …এবং যদি উপাদানটির পরবর্তী সংস্করণ অভ্যন্তরীণ বাস্তবায়নের বিবরণ পরিবর্তন করে তাহলে পৃষ্ঠাগুলি ভাঙবে না।

এর মানে কি আপনাকে সিদ্ধান্ত নিতে হবে কখন HTML/JavaScript ব্যবহার করবেন এবং কখন ওয়েব কম্পোনেন্ট ব্যবহার করবেন? না! এইচটিএমএল এবং জাভাস্ক্রিপ্ট ইন্টারেক্টিভ ভিজ্যুয়াল স্টাফ তৈরি করতে পারে। উইজেট হল ইন্টারেক্টিভ ভিজ্যুয়াল স্টাফ। একটি উইজেট তৈরি করার সময় আপনার এইচটিএমএল এবং জাভাস্ক্রিপ্ট দক্ষতার ব্যবহার করা বোধগম্য। ওয়েব কম্পোনেন্ট স্ট্যান্ডার্ডগুলি আপনাকে এটি করতে সহায়তা করার জন্য ডিজাইন করা হয়েছে।

কিন্তু একটি মৌলিক সমস্যা রয়েছে যা এইচটিএমএল এবং জাভাস্ক্রিপ্ট থেকে তৈরি উইজেটগুলিকে ব্যবহার করা কঠিন করে তোলে: একটি উইজেটের ভিতরের DOM ট্রি বাকি পৃষ্ঠা থেকে এনক্যাপসুলেট করা হয় না। এনক্যাপসুলেশনের এই অভাব মানে আপনার নথির স্টাইলশীট দুর্ঘটনাক্রমে উইজেটের ভিতরের অংশগুলিতে প্রযোজ্য হতে পারে; আপনার জাভাস্ক্রিপ্ট ভুলবশত উইজেটের ভিতরের অংশগুলি পরিবর্তন করতে পারে; আপনার আইডিগুলি উইজেটের ভিতরে থাকা আইডিগুলির সাথে ওভারল্যাপ হতে পারে; এবং তাই

ওয়েব উপাদান তিনটি অংশ নিয়ে গঠিত:

  1. টেমপ্লেট
  2. ছায়া DOM
  3. কাস্টম উপাদান

ছায়া DOM DOM ট্রি এনক্যাপসুলেশন সমস্যার সমাধান করে। ওয়েব কম্পোনেন্টের চারটি অংশ একসাথে কাজ করার জন্য ডিজাইন করা হয়েছে, তবে আপনি ওয়েব কম্পোনেন্টের কোন অংশগুলি ব্যবহার করবেন তাও বেছে নিতে পারেন। এই টিউটোরিয়ালটি আপনাকে দেখায় কিভাবে Shadow DOM ব্যবহার করতে হয়।

হ্যালো, ছায়া বিশ্ব

Shadow DOM এর সাথে, উপাদানগুলি তাদের সাথে যুক্ত একটি নতুন ধরণের নোড পেতে পারে। এই নতুন ধরনের নোডকে শ্যাডো রুট বলা হয়। যে উপাদানটির সাথে শ্যাডো রুট যুক্ত থাকে তাকে শ্যাডো হোস্ট বলে। একটি ছায়া হোস্টের বিষয়বস্তু রেন্ডার করা হয় না; ছায়া রুটের বিষয়বস্তু পরিবর্তে রেন্ডার করা হয়।

উদাহরণস্বরূপ, যদি আপনি এই মত মার্কআপ ছিল:

<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
</script>

তারপর পরিবর্তে

<button id="ex1a">Hello, world!</button>
<script>
function remove(selector) {
  Array.prototype.forEach.call(
      document.querySelectorAll(selector),
      function (node) { node.parentNode.removeChild(node); });
}

if (!HTMLElement.prototype.createShadowRoot) {
  remove('#ex1a');
  document.write('<img src="SS1.png" alt="Screenshot of a button with \'Hello, world!\' on it.">');
}
</script>

আপনার পৃষ্ঠার মত দেখাচ্ছে

<button id="ex1b">Hello, world!</button>
<script>
(function () {
  if (!HTMLElement.prototype.createShadowRoot) {
    remove('#ex1b');
    document.write('<img src="SS2.png" alt="Screenshot of a button with \'Hello, shadow world!\' in Japanese on it.">');
    return;
  }
  var host = document.querySelector('#ex1b');
  var root = host.createShadowRoot();
  root.textContent = 'こんにちは、影の世界!';
})();
</script>

শুধু তাই নয়, যদি পৃষ্ঠায় জাভাস্ক্রিপ্ট জিজ্ঞাসা করে যে বোতামটির textContent কী, এটি "こんにちは、影の世界!" পাবে না, কিন্তু "হ্যালো, বিশ্ব!" কারণ ছায়া রুটের নিচে DOM সাবট্রি এনক্যাপসুলেটেড।

উপস্থাপনা থেকে বিষয়বস্তু পৃথক করা

এখন আমরা উপস্থাপনা থেকে বিষয়বস্তু আলাদা করতে Shadow DOM ব্যবহার করে দেখব। ধরা যাক আমাদের এই নামের ট্যাগ আছে:

<style>
.ex2a.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.ex2a .boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.ex2a .name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="ex2a outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

এখানে মার্কআপ. এই আপনি আজ লিখতে চান কি. এটি ছায়া DOM ব্যবহার করে না:

<style>
.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

যেহেতু DOM গাছে এনক্যাপসুলেশন নেই, তাই নামের ট্যাগের সম্পূর্ণ কাঠামোটি নথিতে উন্মুক্ত করা হয়েছে। যদি পৃষ্ঠার অন্যান্য উপাদান ভুলবশত স্টাইলিং বা স্ক্রিপ্টিংয়ের জন্য একই শ্রেণীর নাম ব্যবহার করে, তাহলে আমাদের একটি খারাপ সময় হবে।

আমরা খারাপ সময় এড়াতে পারি।

ধাপ 1: উপস্থাপনা বিবরণ লুকান

শব্দার্থগতভাবে আমরা সম্ভবত শুধুমাত্র এটি যত্ন করি:

  • এটি একটি নাম ট্যাগ.
  • নাম "বব"।

প্রথমত, আমরা মার্কআপ লিখি যা আমরা চাই সত্যিকারের শব্দার্থের কাছাকাছি:

<div id="nameTag">Bob</div>

তারপরে আমরা উপস্থাপনার জন্য ব্যবহৃত সমস্ত শৈলী এবং ডিভগুলিকে একটি <template> উপাদানে রাখি:

<div id="nameTag">Bob</div>
<template id="nameTagTemplate">
<span class="unchanged"><style>
.outer {
  border: 2px solid brown;

  … same as above …

</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div></span>
</template>

এই মুহুর্তে 'বব' একমাত্র জিনিস যা রেন্ডার করা হয়। যেহেতু আমরা উপস্থাপনামূলক DOM উপাদানগুলিকে একটি <template> উপাদানের মধ্যে স্থানান্তরিত করেছি, সেগুলি রেন্ডার করা হয় না, তবে সেগুলি জাভাস্ক্রিপ্ট থেকে অ্যাক্সেস করা যেতে পারে । আমরা এখন ছায়া রুট পপুলেট করতে এটি করি:

<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);

এখন আমরা একটি ছায়া রুট সেট আপ করেছি, নাম ট্যাগ আবার রেন্ডার করা হয়েছে। আপনি যদি নামের ট্যাগে ডান-ক্লিক করেন এবং উপাদানটি পরিদর্শন করেন তবে আপনি দেখতে পাচ্ছেন যে এটি মিষ্টি, শব্দার্থিক মার্কআপ:

<div id="nameTag">Bob</div>

এটি প্রমাণ করে যে, Shadow DOM ব্যবহার করে, আমরা নথি থেকে নাম ট্যাগের উপস্থাপনা বিবরণ লুকিয়ে রেখেছি। প্রেজেন্টেশনের বিবরণ শ্যাডো DOM-এ এনক্যাপসুলেট করা হয়েছে।

ধাপ 2: উপস্থাপনা থেকে বিষয়বস্তু আলাদা করুন

আমাদের নামের ট্যাগটি এখন পৃষ্ঠা থেকে উপস্থাপনার বিবরণ লুকিয়ে রাখে, কিন্তু এটি আসলে বিষয়বস্তু থেকে উপস্থাপনাকে আলাদা করে না, কারণ যদিও বিষয়বস্তু (নাম "বব") পৃষ্ঠায় রয়েছে, তবে যে নামটি রেন্ডার করা হয়েছে সেটিই আমরা কপি করেছি ছায়া গোড়া যদি আমরা নামের ট্যাগের নাম পরিবর্তন করতে চাই, তাহলে আমাদের এটি দুটি জায়গায় করতে হবে, এবং সেগুলি সিঙ্ক থেকে বেরিয়ে যেতে পারে।

HTML উপাদানগুলি রচনামূলক — আপনি একটি টেবিলের ভিতরে একটি বোতাম রাখতে পারেন, উদাহরণস্বরূপ। রচনাটি আমাদের এখানে প্রয়োজন: নামের ট্যাগটি অবশ্যই লাল পটভূমির একটি রচনা হতে হবে, "হাই!" পাঠ্য, এবং নাম ট্যাগে থাকা বিষয়বস্তু।

আপনি, উপাদান লেখক, <content> নামক একটি নতুন উপাদান ব্যবহার করে আপনার উইজেটের সাথে কম্পোজিশন কীভাবে কাজ করে তা নির্ধারণ করুন। এটি উইজেটের উপস্থাপনায় একটি সন্নিবেশ বিন্দু তৈরি করে, এবং সন্নিবেশ পয়েন্টটি সেই বিন্দুতে উপস্থাপন করার জন্য ছায়া হোস্ট থেকে সামগ্রী বাছাই করে।

যদি আমরা ছায়া DOM-এ মার্কআপ পরিবর্তন করি:

<span class="unchanged"><template id="nameTagTemplate">
<style>
  …
</style></span>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    <content></content>
  </div>
</div>
<span class="unchanged"></template></span>

যখন নামের ট্যাগটি রেন্ডার করা হয়, তখন ছায়া হোস্টের বিষয়বস্তুটি সেই স্থানে প্রজেক্ট করা হয় যেখানে <content> উপাদানটি উপস্থিত হয়।

এখন নথির গঠনটি সহজ কারণ নামটি শুধুমাত্র একটি জায়গায় রয়েছে - নথিটি। যদি আপনার পৃষ্ঠায় কখনও ব্যবহারকারীর নাম আপডেট করার প্রয়োজন হয়, আপনি শুধু লিখুন:

document.querySelector('#nameTag').textContent = 'Shellie';

এবং যে এটা. নাম ট্যাগের রেন্ডারিং ব্রাউজার দ্বারা স্বয়ংক্রিয়ভাবে আপডেট হয়, কারণ আমরা নাম ট্যাগের বিষয়বস্তুকে <content> এর সাথে প্রজেক্ট করছি

<div id="ex2b">

এখন আমরা বিষয়বস্তু এবং উপস্থাপনার পৃথকীকরণ অর্জন করেছি। বিষয়বস্তু নথিতে আছে; উপস্থাপনাটি ছায়া DOM-এ রয়েছে। যখন কিছু রেন্ডার করার সময় আসে তখন সেগুলি ব্রাউজার দ্বারা স্বয়ংক্রিয়ভাবে সিঙ্কে রাখা হয়।

ধাপ 3: লাভ

বিষয়বস্তু এবং উপস্থাপনাকে আলাদা করে, আমরা সেই কোডটিকে সরলীকরণ করতে পারি যা বিষয়বস্তুকে ম্যানিপুলেট করে — নামের ট্যাগের উদাহরণে, সেই কোডটিকে শুধুমাত্র একাধিকের পরিবর্তে একটি <div> সমন্বিত একটি সাধারণ কাঠামোর সাথে মোকাবিলা করতে হবে।

এখন আমরা যদি আমাদের উপস্থাপনা পরিবর্তন করি, তাহলে আমাদের কোনো কোড পরিবর্তন করতে হবে না!

উদাহরণস্বরূপ, বলুন আমরা আমাদের নাম ট্যাগ স্থানীয়করণ করতে চাই। এটি এখনও একটি নাম ট্যাগ, তাই নথিতে শব্দার্থিক বিষয়বস্তু পরিবর্তন হয় না:

<div id="nameTag">Bob</div>

শ্যাডো রুট সেটআপ কোড একই থাকে। ছায়ামূলে যা রাখা হয় তা পরিবর্তন হয়:

<template id="nameTagTemplate">
<style>
.outer {
  border: 2px solid pink;
  border-radius: 1em;
  background: url(sakura.jpg);
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
  font-family: sans-serif;
  font-weight: bold;
}
.name {
  font-size: 45pt;
  font-weight: normal;
  margin-top: 0.8em;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="name">
    <content></content>
  </div>
  と申します。
</div>
</template>

আজকের ওয়েবে পরিস্থিতির উপর এটি একটি বড় উন্নতি, কারণ আপনার নাম আপডেট কোড উপাদানটির গঠনের উপর নির্ভর করতে পারে যা সহজ এবং সামঞ্জস্যপূর্ণ। আপনার নাম আপডেট কোড রেন্ডারিংয়ের জন্য ব্যবহৃত কাঠামো জানার প্রয়োজন নেই। আমরা যদি রেন্ডার করা হয় তা বিবেচনা করলে, নামটি ইংরেজিতে দ্বিতীয় (“Hi! My name is” এর পরে), কিন্তু জাপানি ভাষায় প্রথম (“と申します”)। যে পার্থক্যটি প্রদর্শিত হচ্ছে তা আপডেট করার দৃষ্টিকোণ থেকে শব্দার্থগতভাবে অর্থহীন, তাই নাম আপডেট কোডটি সেই বিশদ সম্পর্কে জানতে হবে না।

অতিরিক্ত ক্রেডিট: অ্যাডভান্সড প্রজেকশন

উপরের উদাহরণে, <content> উপাদান চেরি-ছায়া হোস্ট থেকে সমস্ত সামগ্রী বাছাই করে। select বৈশিষ্ট্য ব্যবহার করে, আপনি একটি বিষয়বস্তুর উপাদান কী প্রজেক্ট করে তা নিয়ন্ত্রণ করতে পারেন। আপনি একাধিক বিষয়বস্তু উপাদান ব্যবহার করতে পারেন.

উদাহরণস্বরূপ, যদি আপনার কাছে এমন একটি নথি থাকে যাতে এটি রয়েছে:

<div id="nameTag">
  <div class="first">Bob</div>
  <div>B. Love</div>
  <div class="email">bob@</div>
</div>

এবং একটি ছায়া রুট যা নির্দিষ্ট বিষয়বস্তু নির্বাচন করতে CSS নির্বাচকদের ব্যবহার করে:

<div style="background: purple; padding: 1em;">
  <div style="color: red;">
    <content **select=".first"**></content>
  </div>
  <div style="color: yellow;">
    <content **select="div"**></content>
  </div>
  <div style="color: blue;">
    <content **select=".email">**</content>
  </div>
</div>

<div class="email"> উপাদানটি <content select="div"> এবং <content select=".email"> এলিমেন্ট উভয়ের সাথে মিলে যায়। ববের ইমেল ঠিকানা কতবার প্রদর্শিত হয় এবং কোন রঙে?

উত্তর হল যে ববের ইমেল ঠিকানাটি একবার প্রদর্শিত হয় এবং এটি হলুদ।

কারণ হল, শ্যাডো ডম হ্যাক করা লোকেরা জানেন যে, পর্দায় আসলে যা রেন্ডার করা হয়েছে তার গাছ তৈরি করা একটি বিশাল পার্টির মতো। বিষয়বস্তু উপাদান হল আমন্ত্রণ যা নথি থেকে বিষয়বস্তুকে ব্যাকস্টেজ শ্যাডো DOM রেন্ডারিং পার্টিতে যেতে দেয়। এই আমন্ত্রণগুলি ক্রমানুসারে বিতরণ করা হয়; কে একটি আমন্ত্রণ পায় তা নির্ভর করে এটি কাকে সম্বোধন করা হয়েছে (অর্থাৎ, select বৈশিষ্ট্য।) সামগ্রী, একবার আমন্ত্রিত হলে, সর্বদা আমন্ত্রণ গ্রহণ করে (কে না করবে?!) এবং এটি বন্ধ হয়ে যায়। যদি পরবর্তী আমন্ত্রণটি আবার সেই ঠিকানায় পাঠানো হয়, ঠিক আছে, বাড়িতে কেউ নেই, এবং এটি আপনার পার্টিতে আসে না।

উপরের উদাহরণে, <div class="email"> div নির্বাচক এবং .email নির্বাচক উভয়ের সাথে মিলে যায়, কিন্তু যেহেতু div নির্বাচক সহ বিষয়বস্তু উপাদানটি নথিতে আগে আসে, <div class="email"> যায় হলুদ পার্টি, এবং কেউ নীল পার্টিতে আসতে পাওয়া যায় না। (এ কারণেই এটি এত নীল, যদিও দুঃখ সঙ্গ পছন্দ করে, তাই আপনি কখনই জানেন না।)

যদি কিছুকে কোন পার্টিতে আমন্ত্রণ জানানো হয়, তবে তা মোটেও রেন্ডার করা হয় না। প্রথম উদাহরণে "হ্যালো, ওয়ার্ল্ড" পাঠ্যের ক্ষেত্রেও তাই ঘটেছে। আপনি যখন একটি আমূল ভিন্ন রেন্ডারিং অর্জন করতে চান তখন এটি কার্যকর: নথিতে শব্দার্থিক মডেলটি লিখুন, যা পৃষ্ঠায় স্ক্রিপ্টগুলিতে অ্যাক্সেসযোগ্য, তবে রেন্ডারিংয়ের উদ্দেশ্যে এটি লুকিয়ে রাখুন এবং এটিকে ছায়া DOM-এ একটি সত্যিই ভিন্ন রেন্ডারিং মডেলের সাথে সংযুক্ত করুন জাভাস্ক্রিপ্ট ব্যবহার করে।

উদাহরণস্বরূপ, এইচটিএমএল একটি চমৎকার তারিখ পিকার আছে. আপনি <input type="date"> লিখলে আপনি একটি ঝরঝরে পপ-আপ ক্যালেন্ডার পাবেন। কিন্তু আপনি যদি ব্যবহারকারীকে তাদের ডেজার্ট দ্বীপ অবকাশের জন্য বিভিন্ন তারিখ বাছাই করতে দিতে চান (আপনি জানেন... রেড ভাইন থেকে তৈরি হ্যামক সহ।) আপনি এইভাবে আপনার নথি সেট আপ করুন:

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

কিন্তু শ্যাডো ডম তৈরি করুন যা একটি টেবিল ব্যবহার করে একটি চটকদার ক্যালেন্ডার তৈরি করে যা তারিখের পরিসর এবং আরও কিছু হাইলাইট করে। ব্যবহারকারী ক্যালেন্ডারের দিনগুলিতে ক্লিক করলে, উপাদানটি startDate এবং endDate ইনপুটগুলিতে অবস্থা আপডেট করে; যখন ব্যবহারকারী ফর্ম জমা দেয়, তখন সেই ইনপুট উপাদানগুলির মানগুলি জমা হয়।

কেন আমি নথিতে লেবেলগুলি অন্তর্ভুক্ত করেছি যদি সেগুলি রেন্ডার করা না হয়? কারণ হল যে একজন ব্যবহারকারী যদি শ্যাডো ডম সমর্থন করে না এমন একটি ব্রাউজার দিয়ে ফর্মটি দেখেন তবে ফর্মটি এখনও ব্যবহারযোগ্য, ঠিক ততটা সুন্দর নয়। ব্যবহারকারী এমন কিছু দেখতে পান:

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

আপনি ছায়া DOM 101 পাস করুন

সেগুলি হল Shadow DOM-এর মূল বিষয়গুলি — আপনি Shadow DOM 101 পাস করেন! আপনি Shadow DOM এর সাথে আরও অনেক কিছু করতে পারেন, উদাহরণস্বরূপ, আপনি একটি শ্যাডো হোস্টে একাধিক ছায়া ব্যবহার করতে পারেন, বা এনক্যাপসুলেশনের জন্য নেস্টেড শ্যাডো ব্যবহার করতে পারেন, অথবা মডেল-ড্রাইভেন ভিউ (MDV) এবং শ্যাডো ডম ব্যবহার করে আপনার পৃষ্ঠার আর্কিটেক্ট করতে পারেন। এবং ওয়েব উপাদানগুলি কেবল ছায়া DOM এর চেয়ে বেশি।

আমরা পরবর্তী পোস্টগুলিতে সেগুলি ব্যাখ্যা করব।