تجاوز العوائق باستخدام واجهة برمجة التطبيقات DataTransfer API

تمكين المستخدم من مشاركة البيانات خارج نافذة المتصفح.

ربما سمعت عن DataTransfer API، وهي جزء من HTML5 Drag and Drop API وأحداث الحافظة. يمكن استخدامها لنقل البيانات بين المصدر والأهداف المستلمة.

توافق المتصفّح

  • Chrome: 3.
  • Edge: 12.
  • Firefox: 3.5
  • Safari: 4.

المصدر

غالبًا ما يتم استخدام تفاعلات السحب والإفلات والنسخ واللصق للتفاعلات داخل الصفحة لنقل نص بسيط من "أ" إلى "ب". ولكن غالبًا ما يتم تجاهله هو القدرة على استخدام هذه التفاعلات نفسها لتجاوز نافذة المتصفح.

يمكن أن تتواصل تفاعلات السحب والإفلات المضمّنة في المتصفّح وعمليات النسخ واللصق مع التطبيقات الأخرى أو الويب أو غير ذلك، وهي ليست مرتبطة بأي مصدر. تتيح واجهة برمجة التطبيقات استخدام العديد من إدخالات البيانات بسلوكيات مختلفة استنادًا إلى المكان الذي يتم نقل البيانات إليه. يمكن لتطبيق الويب إرسال واستقبال البيانات المنقولة عند الاستماع إلى الأحداث الواردة.

وقد تغيّر هذه الإمكانية طريقة تفكيرنا في المشاركة وإمكانية التشغيل التفاعلي في تطبيقات الويب على أجهزة الكمبيوتر المكتبي. لم يعُد نقل البيانات بين التطبيقات يعتمد على عمليات دمج مرتبطة ببعضها بشكل وثيق. بدلاً من ذلك، يمكنك منح المستخدمين التحكم الكامل في نقل البيانات إلى أي مكان يريدونه.

مثال على التفاعلات الممكنة باستخدام DataTransfer API (لا يتضمّن الفيديو صوتًا).

نقل البيانات

للبدء، ستحتاج إلى تنفيذ السحب والإفلات أو النسخ واللصق. توضح الأمثلة أدناه تفاعلات السحب والإفلات، لكن عملية النسخ واللصق متشابهة. إذا لم تكن على دراية بواجهة برمجة التطبيقات Drag and Drop API، يمكنك الاطّلاع على مقالة رائعة توضّح ميزة HTML5 Drag and Drop.

من خلال تقديم بيانات مفتاح MIME-type، يمكنك التفاعل بحرية مع التطبيقات الخارجية. تستجيب معظم محرّرات WYSIWYG ومحرّرات النصوص والمتصفحات لأنواع mime "البسيطة" المستخدَمة في المثال أدناه.

document.querySelector('#dragSource')
.addEventListener('dragstart', (event) => {
  event.dataTransfer.setData('text/plain', 'Foo bar');
  event.dataTransfer.setData('text/html', '<h1>Foo bar</h1>');
  event.dataTransfer.setData('text/uri-list', 'https://example.com');
});

لاحِظ السمة event.dataTransfer. يعرض هذا الإجراء مثيلًا من DataTransfer. كما ترى، يتم إرجاع هذا الكائن أحيانًا بواسطة خصائص بأسماء أخرى.

تعمل عملية تلقّي نقل البيانات بالطريقة نفسها تقريبًا التي تعمل بها عملية تقديم البيانات. استمع إلى أحداث الاستلام (drop أو paste) واقرأ المفاتيح. عند السحب فوق عنصر، لا يمكن للمتصفّح الوصول إلا إلى مفاتيح type للبيانات. لا يمكن الوصول إلى البيانات نفسها إلا بعد حدوث انخفاض.

document.querySelector('#dropTarget')
.addEventListener('dragover', (event) => {
  console.log(event.dataTransfer.types);
  // Without this, the drop event won't fire.
  event.preventDefault();
});

document.querySelector('#dropTarget')
.addEventListener('drop', (event) => {
  // Log all the transferred data items to the console.
  for (let type of event.dataTransfer.types) {
    console.log({ type, data: event.dataTransfer.getData(type) });
  }
  event.preventDefault();
});

تتوفّر ثلاثة أنواع من MIME على نطاق واسع في جميع التطبيقات:

  • text/html: يعرض حمولة HTML في عناصر contentEditable وأدوات تحرير النصوص المنسّقة (WYSIWYG) مثل "مستندات Google" وMicrosoft Word وغيرها.
  • text/plain: لضبط قيمة عناصر الإدخال ومحتوى أدوات تحرير الرموز والعناصر الاحتياطية من text/html
  • text/uri-list: للانتقال إلى عنوان URL عند ظهوره في شريط عنوان URL أو صفحة المتصفّح. سيتم إنشاء اختصار لعنوان URL عند إفلاته في دليل أو على سطح المكتب.

إن الاعتماد على نطاق واسع لـ text/html من قِبل محرري WYSIWYG يجعله مفيدًا للغاية. كما هو الحال في مستندات HTML، يمكنك تضمين الموارد باستخدام عناوين URL للبيانات أو عناوين URL متاحة للجميع. يعمل ذلك بشكل جيد مع تصدير العناصر المرئية (على سبيل المثال من لوحة) إلى محرّرات مثل "مستندات Google".

const redPixel = 'data:image/gif;base64,R0lGODdhAQABAPAAAP8AAAAAACwAAAAAAQABAAACAkQBADs=';
const html = '<img src="' + redPixel + '" width="100" height="100" alt="" />';
event.dataTransfer.setData('text/html', html);

النقل باستخدام النسخ واللصق

يتم توضيح استخدام واجهة برمجة التطبيقات DataTransfer API مع تفاعلات النسخ واللصق أدناه. لاحِظ أنّ الكائن DataTransfer يتم إرجاعه من خلال سمة تُسمّى clipboardData لأحداث الحافظة.

// Listen to copy-paste events on the document.
document.addEventListener('copy', (event) => {
  const copySource = document.querySelector('#copySource');
  // Only copy when the `activeElement` (i.e., focused element) is,
  // or is within, the `copySource` element.
  if (copySource.contains(document.activeElement)) {
    event.clipboardData.setData('text/plain', 'Foo bar');
    event.preventDefault();
  }
});

document.addEventListener('paste', (event) => {
  const pasteTarget = document.querySelector('#pasteTarget');
  if (pasteTarget.contains(document.activeElement)) {
    const data = event.clipboardData.getData('text/plain');
    console.log(data);
  }
});

تنسيقات البيانات المخصّصة

لستَ مقيّدًا بأنواع MIME الأساسية، بل يمكنك استخدام أي مفتاح لتحديد البيانات المنقولة. ويمكن أن يكون ذلك مفيدًا للتفاعلات على جميع المتصفّحات داخل تطبيقك. يمكنك نقل بيانات أكثر تعقيدًا باستخدام الدالتَين JSON.stringify() وJSON.parse()، كما هو موضّح أدناه.

document.querySelector('#dragSource')
.addEventListener('dragstart', (event) => {
  const data = { foo: 'bar' };
  event.dataTransfer.setData('my-custom-type', JSON.stringify(data));
});

document.querySelector('#dropTarget')
.addEventListener('dragover', (event) => {
  // Only allow dropping when our custom data is available.
  if (event.dataTransfer.types.includes('my-custom-type')) {
    event.preventDefault();
  }
});

document.querySelector('#dropTarget')
.addEventListener('drop', (event) => {
  if (event.dataTransfer.types.includes('my-custom-type')) {
    event.preventDefault();
    const dataString = event.dataTransfer.getData('my-custom-type');
    const data = JSON.parse(dataString);
    console.log(data);
  }
});

ربط الويب

على الرغم من أنّ التنسيقات المخصّصة رائعة للتواصل بين التطبيقات التي يمكنك التحكّم فيها، إلا أنّها تفرض أيضًا قيودًا على المستخدم عند نقل البيانات إلى التطبيقات التي لا تستخدم التنسيق الخاص بك. إذا كنت ترغب في الاتصال بتطبيقات تابعة لجهات خارجية عبر الويب، فأنت بحاجة إلى تنسيق بيانات عام.

ويتناسب معيار JSON-LD (البيانات المرتبطة) بهذا الشكل. وهي خفيفة الوزن وسهلة القراءة والكتابة بها في JavaScript. يحتوي Schema.org على العديد من الأنواع المحدّدة مسبقًا التي يمكن استخدامها، كما تتوفّر خيارات لتعريفات المخططات المخصّصة.

const data = {
  '@context': 'https://schema.org',
  '@type': 'ImageObject',
  contentLocation: 'Venice, Italy',
  contentUrl: 'venice.jpg',
  datePublished: '2010-08-08',
  description: 'I took this picture during our honey moon.',
  name: 'Canal in Venice',
};
event.dataTransfer.setData('application/ld+json', JSON.stringify(data));

عند استخدام أنواع Schema.org، يمكنك البدء بالنوع العام Thing، أو استخدام شيء أقرب إلى حالة استخدامك، مثل Event أو Person أو MediaObject أو Place أو حتى أنواع محدّدة للغاية مثل MedicalEntity إذا لزم الأمر. عند استخدام TypeScript، يمكنك استخدام تعريفات الواجهة من تعريفات النوع schema-dts.

من خلال نقل بيانات JSON-LD وتلقّيها، سيكون بإمكانك توفير شبكة ويب أكثر اتصالًا وانفتاحًا. باستخدام التطبيقات التي تستخدم اللغة نفسها، يمكنك إنشاء عمليات دمج وثيقة مع التطبيقات الخارجية. ولا حاجة إلى عمليات دمج معقدة لواجهات برمجة التطبيقات، فجميع المعلومات المطلوبة مضمّنة في البيانات المنقولة.

فكّر في كل الاحتمالات لنقل البيانات بين أي تطبيق (ويب) بدون أي قيود، مثل مشاركة الأحداث من التقويم إلى تطبيق ToDo المفضل لديك وإرفاق ملفات افتراضية إلى رسائل البريد الإلكتروني ومشاركة جهات الاتصال. هل هذا مناسب لك؟ يبدأ ذلك منك. 🙌

المخاوف والمشاكل الطبية

على الرغم من توفّر DataTransfer API حاليًا، هناك بعض الأمور التي يجب أخذها في الاعتبار قبل الدمج.

توافُق المتصفح

تتوافق جميع متصفّحات أجهزة الكمبيوتر المكتبي بشكلٍ رائع مع التقنية الموضّحة أعلاه، في حين لا تتوافق الأجهزة الجوّالة معها. تم اختبار هذه التقنية على جميع المتصفحات الرئيسية (Chrome وEdge وFirefox وSafari) وأنظمة التشغيل (Android وChromeOS وiOS وmacOS وUbuntu Linux وWindows)، لكن للأسف لم تجتز Android وiOS الاختبار. مع استمرار تطوير المتصفّحات، تقتصر هذه التقنية في الوقت الحالي على متصفّحات أجهزة الكمبيوتر المكتبي فقط.

سهولة العثور على المحتوى

إنّ ميزة السحب والإفلات وميزة النسخ واللصق هما تفاعلان على مستوى النظام عند العمل على جهاز كمبيوتر مكتبي، ويعودان أصولهما إلى واجهة المستخدم الرسومية الأولى التي تم إنشاؤها قبل أكثر من 40 عامًا. فكِّر في عدد المرات التي استخدمت فيها هذه التفاعلات لتنظيم الملفات. وهذا ليس شائعًا على الويب إلى الآن.

عليك إبلاغ المستخدمين بهذا التفاعل الجديد، وإنشاء أنماط لتجربة المستخدم لكي يتم التعرّف عليه، خاصةً للأشخاص الذين اقتصرت تجربتهم مع أجهزة الكمبيوتر حتى الآن على الأجهزة الجوّالة.

تسهيل الاستخدام

لا يُعد السحب والإفلات تفاعلاً يسهل الوصول إليه، ولكن واجهة برمجة التطبيقات DataTransfer API تعمل مع النسخ واللصق أيضًا. تأكَّد من رصد أحداث النسخ واللصق. لا يتطلب الأمر الكثير من العمل الإضافي، وسيكون المستخدمون ممتنين لك لإضافتها.

الأمان والخصوصية

هناك بعض اعتبارات الأمان والخصوصية التي يجب أن تكون على دراية بها عند استخدام هذا الأسلوب.

  • تتوفّر بيانات الحافظة للتطبيقات الأخرى على جهاز المستخدم.
  • يمكن لتطبيقات الويب التي تسحبها الوصول إلى مفاتيح النوع، وليس البيانات. تصبح البيانات متوفرة فقط عند الإفلات أو اللصق.
  • يجب التعامل مع البيانات التي تم استلامها مثل أي بيانات إدخال أخرى للمستخدم، لذا يجب تعقيمها والتحقّق من صحتها قبل الاستخدام.

بدء استخدام مكتبة Transmat المساعِدة

هل يهمّك استخدام DataTransfer API في تطبيقك؟ ننصحك بالاطّلاع على مكتبة Transmat على GitHub. تعمل هذه المكتبة المفتوحة المصدر على مواءمة الاختلافات بين المتصفِّحات، وتوفير برامج خدمات JSON-LD، وتحتوي على أداة مراقبة للاستجابة لأحداث النقل من أجل إبراز المناطق غير القابلة للتقديم أو الإيقاف، وتتيح لك دمج عمليات نقل البيانات بين عمليات السحب والإفلات الحالية.

import { Transmat, TransmatObserver, addListeners } from 'transmat';

// Send data on drag/copy.
addListeners(myElement, 'transmit', (event) => {
  const transmat = new Transmat(event);
  transmat.setData({
    'text/plain': 'Foobar',
    'application/json': { foo: 'bar' },
  });
});

// Receive data on drop/paste.
addListeners(myElement, 'receive', (event) => {
  const transmat = new Transmat(event);
  if (transmat.hasType('application/json') && transmat.accept()) {
    const data = JSON.parse(transmat.getData('application/json'));
  }
});

// Observe transfer events and highlight drop areas.
const obs = new TransmatObserver((entries) => {
  for (const entry of entries) {
    const transmat = new Transmat(entry.event);
    if (transmat.hasMimeType('application/json')) {
      entry.target.classList.toggle('drag-over', entry.isTarget);
      entry.target.classList.toggle('drag-active', entry.isActive);
    }
  }
});
obs.observe(myElement);

شكر وتقدير

الصورة الرئيسية تقدّمها لوبا إرتيل على Unsplash.