ديثي إلزا ( [email protected] )، مهندس فني أول، Blast Radius
يعد نموذج كائن المستند (DOM) أحد الأدوات الأكثر استخدامًا لمعالجة بيانات XML وHTML، ومع ذلك نادرًا ما يتم استغلال إمكاناته بشكل كامل. من خلال الاستفادة من DOM وتسهيل استخدامه، يمكنك الحصول على أداة قوية لتطبيقات XML، بما في ذلك تطبيقات الويب الديناميكية.
يضم هذا العدد كاتب عمود ضيفًا وصديقًا وزميلًا ديثي إلزا. يتمتع Dethe بخبرة واسعة في تطوير تطبيقات الويب باستخدام XML، وأود أن أشكره على مساعدتي في تقديم برمجة XML باستخدام DOM وECMAScript. ترقبوا هذا العمود لمزيد من أعمدة Dethe.
-
يعد David Mertz DOM أحد واجهات برمجة التطبيقات القياسية لمعالجة XML وHTML. غالبًا ما يتم انتقادها لكونها كثيفة الذاكرة وبطيئة ومطولة. ومع ذلك، فهو الخيار الأفضل للعديد من التطبيقات، وهو بالتأكيد أبسط من SAX، واجهة برمجة التطبيقات الرئيسية الأخرى لـ XML. يظهر DOM تدريجيًا في أدوات مثل متصفحات الويب ومتصفحات SVG وOpenOffice وما إلى ذلك.
يعد DOM رائعًا لأنه معيار ويتم تنفيذه على نطاق واسع ومدمج في معايير أخرى. كمعيار، فإن تعاملها مع البيانات هو لغة برمجية حيادية (والتي قد تكون أو لا تكون قوة، ولكنها على الأقل تجعل الطريقة التي نتعامل بها مع البيانات متسقة). لم يعد DOM الآن مدمجًا في متصفحات الويب فحسب، بل أصبح أيضًا جزءًا من العديد من المواصفات المستندة إلى XML. والآن بعد أن أصبحت جزءًا من ترسانتك، وربما لا تزال تستخدمها من حين لآخر، أعتقد أن الوقت قد حان للاستفادة الكاملة مما تقدمه على الطاولة.
بعد العمل مع DOM لفترة من الوقت، سترى الأنماط تتطور - الأشياء التي تريد القيام بها مرارًا وتكرارًا. تساعدك الاختصارات على العمل مع DOMs الطويلة وإنشاء تعليمات برمجية أنيقة وواضحة. فيما يلي مجموعة من النصائح والحيل التي أستخدمها بشكل متكرر، بالإضافة إلى بعض أمثلة JavaScript.
الخدعة الأولى
التي يتم إدخالها بعد وprependChild
هي أنه "لا توجد خدعة".لدى DOM طريقتان لإضافة عقدة فرعية إلى عقدة حاوية (عادةً عنصر أو مستند أو جزء مستند): appendChild(node) وinsertBefore(node, ReferenceNode). يبدو أن هناك شيئا مفقودا. ماذا لو كنت أرغب في إدراج عقدة فرعية أو إضافتها مسبقًا بعد العقدة المرجعية (مما يجعل العقدة الجديدة هي الأولى في القائمة)؟ لسنوات عديدة، كان الحل هو كتابة الوظيفة التالية:
القائمة 1. الطرق الخاطئة للإدراج والإضافة بواسطة
وظيفة InsertAfter(parent,node,referenceNode) {
إذا (referenceNode.nextSibling) {
parent.insertBefore(node,referenceNode.nextSibling);
} آخر {
parent.appendChild(node);
}
}
دالة prependChild(parent,node) {
إذا (الوالد.الطفل الأول) {
parent.insertBefore(node,parent.firstChild);
} آخر {
parent.appendChild(node);
}
}
في الواقع، مثل القائمة 1، تم تعريف الدالة InsertBefore() للعودة إلى appendChild() عندما تكون العقدة المرجعية فارغة. لذلك بدلاً من استخدام الطرق المذكورة أعلاه، يمكنك استخدام الطرق الموجودة في القائمة 2، أو تخطيها واستخدام الوظائف المضمنة فقط:
القائمة 2. الطريقة الصحيحة للإدراج والإضافة من قبل
وظيفة InsertAfter(parent,node,referenceNode) {
parent.insertBefore(node,referenceNode.nextSibling);
}
دالة prependChild(parent,node) {
parent.insertBefore(node,parent.firstChild);
}
إذا كنت جديدًا في برمجة DOM، فمن المهم الإشارة إلى أنه على الرغم من أنه يمكن أن يكون لديك مؤشرات متعددة تشير إلى عقدة، إلا أن هذه العقدة يمكن أن توجد فقط في موقع واحد في شجرة DOM. لذلك إذا كنت تريد إدخاله في الشجرة، فلا داعي لإزالته من الشجرة أولاً حيث سيتم إزالته تلقائيًا. تعتبر هذه الآلية ملائمة عند إعادة ترتيب العقد بمجرد إدخالها في مواضعها الجديدة.
وفقًا لهذه الآلية، إذا كنت تريد تبديل مواضع عقدتين متجاورتين (تسمى العقدة 1 والعقدة 2)، فيمكنك استخدام أحد الحلول التالية:
node1.parentNode.insertBefore(node2,node1);
أو
(
node1.nextSibling,node1);
يستخدم DOM على نطاق واسع في صفحات الويب. إذا قمت بزيارة موقع bookmarklets (راجع الموارد)، فستجد العديد من النصوص الإبداعية القصيرة التي يمكنها إعادة ترتيب الصفحات واستخراج الروابط وإخفاء الصور أو إعلانات الفلاش والمزيد.
ومع ذلك، نظرًا لأن Internet Explorer لا يحدد ثوابت واجهة العقدة التي يمكن استخدامها لتحديد أنواع العقد، فيجب عليك التأكد من أنه إذا قمت بحذف ثابت الواجهة، فيجب عليك أولاً تحديد ثابت الواجهة في البرنامج النصي DOM للويب.
القائمة 3. التأكد من تعريف العقدة
إذا (!نافذة['عقدة']) {
window.Node = new Object();
Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3؛
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9؛
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11؛
Node.NOTATION_NODE = 12;
}
توضح القائمة 4 كيفية استخراج كافة العقد النصية الموجودة في العقدة:
القائمة 4. النص الداخلي
وظيفة النص الداخلي (عقدة) {
// هل هذه عقدة نصية أم CDATA؟
إذا (node.nodeType == 3 || العقدة.nodeType == 4) {
إرجاع العقدة. البيانات؛
}
فار ط؛
فار returnValue = [];
for (i = 0; i <node.childNodes.length; i++) {
returnValue.push(innerText(node.childNodes[i]));
}
إرجاع returnValue.join('');
}
الاختصارات
غالبًا ما يشتكي الأشخاص من أن DOM مطول جدًا وأن الوظائف البسيطة تتطلب الكثير من التعليمات البرمجية. على سبيل المثال، إذا أردت إنشاء عنصر <div> يحتوي على نص ويستجيب لنقرة زر، فقد يبدو الرمز كما يلي:
القائمة 5. "الطريق الطويل" لإنشاء عنصر <div>
وظيفة Handle_button() {
varparent = document.getElementById('myContainer');
var div = document.createElement('div');
div.className = 'myDivCSSClass';
div.id = 'myDivId';
div.style.position = 'مطلق';
div.style.left = '300px';
div.style.top = '200px';
var text = "هذا هو النص الأول لبقية هذا الكود";
var textNode = document.createTextNode(text);
div.appendChild(textNode);
parent.appendChild(div);
}
إذا قمت بإنشاء العقد بهذه الطريقة بشكل متكرر، فإن كتابة كل هذا الرمز سوف يتعبك بسرعة. كان لا بد من وجود حل أفضل - وكان هناك! إليك أداة تساعدك على إنشاء العناصر، وتعيين خصائص العناصر وأنماطها، وإضافة عقد فرعية نصية. باستثناء معلمة الاسم، تكون جميع المعلمات الأخرى اختيارية.
القائمة 6. اختصار وظيفة العنصر ().
عنصر الوظيفة (الاسم، السمات، النمط، النص) {
var e = document.createElement(name);
إذا (اترز) {
لـ (مفتاح في attrs) {
إذا (مفتاح == 'فئة') {
e.className = attrs[key];
} وإلا إذا (مفتاح == 'معرف') {
e.id = attrs[key];
} آخر {
e.setAttribute(key, attrs[key]);
}
}
}
إذا (النمط) {
لـ (مفتاح في الأسلوب) {
e.style[key] = style[key];
}
}
إذا (نص) {
e.appendChild(document.createTextNode(text));
}
العودة ه.
}
باستخدام هذا الاختصار، يمكنك إنشاء عنصر <div> في القائمة 5 بطريقة أكثر إيجازًا. لاحظ أنه يتم توفير معلمات attrs والنمط باستخدام كائنات نص JavaScript.
القائمة 7. طريقة سهلة لإنشاء <div>
وظيفة Handle_button() {
varparent = document.getElementById('myContainer');
Parent.appendChild(elem('div',
{الفئة: 'myDivCSSClass'، المعرف: 'myDivId'}
{الموضع: 'مطلق'، اليسار: '300 بكسل'، الأعلى: '200 بكسل'}،
'هذا هو النص الأول لبقية هذا الكود'))؛
}
يمكن أن توفر لك هذه الأداة المساعدة الكثير من الوقت عندما تريد إنشاء عدد كبير من كائنات DHTML المعقدة بسرعة. يعني النمط هنا أنه إذا كان لديك بنية DOM محددة تحتاج إلى إنشائها بشكل متكرر، فاستخدم أداة مساعدة لإنشائها. لا يؤدي هذا إلى تقليل كمية التعليمات البرمجية التي تكتبها فحسب، بل يقلل أيضًا من تكرار قص التعليمات البرمجية ولصقها (سبب الأخطاء) ويجعل من السهل التفكير بوضوح عند قراءة التعليمات البرمجية.
ما هي الخطوة التالية؟
غالبًا ما يواجه DOM صعوبة في إخبارك بالعقدة التالية بترتيب المستند. فيما يلي بعض الأدوات المساعدة التي تساعدك على التحرك للأمام والخلف بين العقد:
القائمة 8.nextNode وprevNode
// إرجاع العقدة التالية بترتيب المستند
الوظيفة التالية العقدة (العقدة) {
إذا (! عقدة) العودة فارغة؛
إذا (العقدة.الطفل الأول){
إرجاع العقدة.firstChild;
} آخر {
إرجاع nextWide(node);
}
}
// وظيفة مساعدة للعقدة التالية ()
الوظيفة التالية واسعة (عقدة) {
إذا (! عقدة) العودة فارغة؛
إذا (العقدة.nextSibling) {
return Node.nextSibling;
} آخر {
return nextWide(node.parentNode);
}
}
// إرجاع العقدة السابقة بترتيب المستند
الوظيفة السابقةالعقدة (العقدة) {
إذا (! عقدة) العودة فارغة؛
إذا (العقدة.previousSibling) {
return PreviousDeep(node.previousSibling);
}
إرجاع العقدة.parentNode;
}
// وظيفة مساعدة للعقدة السابقة ()
الوظيفة السابقةDeep(عقدة) {
إذا (! عقدة) العودة فارغة؛
بينما (node.childNodes.length) {
العقدة = العقدة.lastChild؛
}
عقدة العودة؛
}
استخدم DOM بسهولة
في بعض الأحيان قد ترغب في التكرار عبر DOM، أو استدعاء دالة على كل عقدة، أو إرجاع قيمة من كل عقدة. في الواقع، نظرًا لأن هذه الأفكار عامة جدًا، يتضمن DOM Level 2 بالفعل امتدادًا يسمى DOM Traversal and Range (الذي يحدد الكائنات وواجهات برمجة التطبيقات لتكرار جميع العقد في DOM)، والذي يُستخدم لتطبيق الوظيفة وتحديد نطاق في DOM . نظرًا لأن هذه الوظائف لم يتم تعريفها في Internet Explorer (على الأقل حتى الآن)، يمكنك استخدام nextNode() للقيام بشيء مماثل.
الفكرة هنا هي إنشاء بعض الأدوات البسيطة والمشتركة ومن ثم تجميعها بطرق مختلفة لتحقيق التأثير المطلوب. إذا كنت معتادًا على البرمجة الوظيفية، فسيبدو هذا مألوفًا. تأخذ مكتبة Beyond JS (انظر الموارد) هذه الفكرة إلى الأمام.
القائمة 9. أدوات DOM الوظيفية
// قم بإرجاع مصفوفة من جميع العقد، بدءًا من startNode و
// الاستمرار خلال بقية شجرة DOM
قائمة الوظائف العقد (startNode) {
قائمة var = مصفوفة جديدة();
var العقدة = startNode;
بينما (العقدة) {
list.push(node);
العقدة = nextNode(node);
}
قائمة العودة؛
}
// مثل listNodes()، ولكنها تعمل بشكل عكسي من startNode.
// لاحظ أن هذا ليس مثل تشغيل listNodes() و
// عكس القائمة.
قائمة الوظائفNodesReversed(startNode) {
قائمة var = مصفوفة جديدة();
var العقدة = startNode;
بينما (العقدة) {
list.push(node);
العقدة = prevNode(node);
}
قائمة العودة؛
}
// تطبيق func على كل عقدة في قائمة العقدة، وإرجاع قائمة جديدة من النتائج
خريطة الوظيفة (قائمة، func) {
var result_list = new Array();
لـ (var i = 0; i < list.length; i++) {
result_list.push(func(list[i]));
}
إرجاع نتيجة_القائمة؛
}
// تطبيق الاختبار على كل عقدة، وإرجاع قائمة جديدة من العقد التي
// الاختبار (العقدة) يُرجع صحيحًا
مرشح الوظيفة (القائمة، الاختبار) {
var result_list = new Array();
لـ (var i = 0; i < list.length; i++) {
إذا (اختبار(قائمة[i])) result_list.push(list[i]);
}
إرجاع نتيجة_القائمة؛
}
تحتوي القائمة 9 على أربع أدوات أساسية. يمكن تمديد الدالتين listNodes() وlistNodesReversed() إلى طول اختياري، على غرار طريقة شريحة() Array، أترك هذا كتمرين لك. شيء آخر يجب ملاحظته هو أن وظائف الخريطة () ومرشح () عامة تمامًا ويمكن استخدامها للعمل مع أي قائمة (وليس فقط قوائم العقد). والآن سأعرض لك بعض الطرق التي يمكن من خلالها الجمع بينهما.
القائمة 10. استخدام المرافق الوظيفية
// قائمة بجميع أسماء العناصر بترتيب المستند
الوظيفة هي العنصر (عقدة) {
returnNode.nodeType == Node.ELEMENT_NODE;
}
اسم العقدة الوظيفية (العقدة) {
إرجاع العقدة.nodeName;
}
var elementNames = Map(filter(listNodes(document),isElement),nodeName);
// كل النص من المستند (يتجاهل CDATA)
الوظيفة نص (عقدة) {
returnNode.nodeType == Node.TEXT_NODE;
}
وظيفة عقدة القيمة (عقدة) {
إرجاع العقدة.nodeValue;
}
var allText = Map(filter(listNodes(document), isText),nodeValue);
يمكنك استخدام هذه الأدوات المساعدة لاستخراج المعرفات وتعديل الأنماط والعثور على عقد معينة وإزالتها والمزيد. بمجرد تنفيذ DOM Traversal وRange APIs على نطاق واسع، يمكنك استخدامها لتعديل شجرة DOM دون إنشاء القائمة أولاً. فهي ليست قوية فحسب، بل إنها تعمل أيضًا بطريقة مشابهة لما أبرزته أعلاه.
منطقة خطر DOM
لاحظ أن واجهة DOM API الأساسية لا تمكنك من تحليل بيانات XML إلى DOM، أو إجراء تسلسل DOM إلى XML. تم تعريف هذه الوظائف في ملحق DOM المستوى 3 "التحميل والحفظ"، لكن لم يتم تنفيذها بالكامل بعد، لذا لا تفكر فيها الآن. كل منصة (متصفح أو أي تطبيق DOM احترافي آخر) لها طريقتها الخاصة في التحويل بين DOM وXML، لكن التحويل عبر الأنظمة الأساسية يقع خارج نطاق هذه المقالة.
DOM ليست أداة آمنة جدًا - خاصةً عند استخدام DOM API لإنشاء أشجار لا يمكن إجراء تسلسل لها بتنسيق XML. لا تخلط مطلقًا بين واجهات برمجة التطبيقات (APIs) غير المخصصة لمساحة الاسم وDOM2 APIs (على سبيل المثال، createElement وcreateElementNS) في نفس البرنامج. إذا كنت تستخدم مساحات الأسماء، فحاول الإعلان عن جميع مساحات الأسماء في موضع العنصر الجذر ولا تتجاوز بادئة مساحة الاسم، وإلا ستصبح الأمور مربكة للغاية. بشكل عام، طالما أنك تتبع روتينك، فلن تتسبب في مواقف حرجة قد تسبب لك المشاكل.
إذا كنت تستخدمInnerText وInnerHTML في Internet Explorer للتحليل، فيمكنك تجربة استخدام الدالة elem(). من خلال إنشاء أدوات مساعدة مماثلة، يمكنك الحصول على المزيد من الراحة وترث فوائد التعليمات البرمجية عبر الأنظمة الأساسية. الخلط بين هاتين الطريقتين سيء للغاية.
لا يتم تضمين بعض أحرف Unicode في XML. يتيح لك تنفيذ DOM إضافتها، لكن النتيجة هي عدم إمكانية إجراء تسلسل لها. تتضمن هذه الأحرف معظم أحرف التحكم والأحرف الفردية في أزواج Unicode البديلة. يحدث هذا فقط إذا حاولت تضمين البيانات الثنائية في المستند، ولكن هذا موقف محرج آخر.
خاتمة
لقد قمت بتغطية الكثير مما يمكن أن يفعله DOM، ولكن هناك الكثير مما يمكن أن يفعله DOM (وجافا سكريبت). قم بدراسة هذه الأمثلة واستكشافها لمعرفة كيف يمكن استخدامها لحل المشكلات التي قد تتطلب نصوصًا برمجية أو قوالب أو واجهات برمجة التطبيقات المتخصصة للعميل.
DOM له حدوده وعيوبه، ولكنه يتمتع أيضًا بالعديد من المزايا: فهو مدمج في العديد من التطبيقات؛ وهو يعمل بنفس الطريقة سواء كنت تستخدم تقنية Java أو Python أو JavaScript، ومن السهل جدًا استخدام القوالب المذكورة أعلاه؛ وهو بسيط وقوي الاستخدام. بدأ عدد متزايد من التطبيقات في دعم DOM، بما في ذلك التطبيقات المستندة إلى Mozilla وOpenOffice وXMetaL من Blast Radius. تتطلب المزيد والمزيد من المواصفات DOM وتوسيعه (على سبيل المثال، SVG)، وبالتالي فإن DOM موجود دائمًا حولك. سيكون من الحكمة استخدام هذه الأداة المنتشرة على نطاق واسع.