مطالعه موردی - دانلود را در کروم بکشید و رها کنید

معرفی

کشیدن و رها کردن (DnD) یکی از بسیاری از ویژگی های عالی HTML 5 است و در فایرفاکس 3.5، سافاری، کروم و اینترنت اکسپلورر پشتیبانی می شود. گوگل اخیرا قابلیت جدیدی را ارائه کرده است که به کاربران گوگل کروم اجازه می دهد فایل ها را از مرورگر به دسکتاپ بکشند و رها کنند. این یک ویژگی بسیار راحت است، اما تا زمانی که رایان سدون مقاله ای در مورد اکتشافات مهندسی معکوس خود در مورد این ویژگی جدید منتشر کرد، به طور گسترده شناخته شده نبود.

در Box.net، ما بسیار هیجان‌زده هستیم که چگونه این قابلیت‌های جدید ما را قادر می‌سازد راه حل مدیریت محتوای ابری خود را بهبود بخشیم، و همچنین به جامعه توسعه‌دهندگان کمک بیشتری کنیم. من خوشحالم که اعلام کنم DnD Download در محصول ما یکپارچه شده است. اکنون، کاربران Box می‌توانند فایل‌ها را مستقیماً از مرورگر کروم به دسکتاپ خود بکشند تا فایل را دانلود و ذخیره کنند.

من می خواهم به اشتراک بگذارم که چگونه در طول توسعه این ویژگی جدید چندین بار تکرار کردم.

Drag and Drop API Support را بررسی کنید

اولین کاری که باید انجام دهید این است که بررسی کنید مرورگر شما به طور کامل از کشیدن و رها کردن HTML5 پشتیبانی می کند. یک راه آسان برای انجام این کار استفاده از کتابخانه ای به نام Modernizr برای بررسی یک ویژگی خاص است:

if (Modernizr.draganddrop) {
// Browser supports native HTML5 DnD.
} else {
// Fallback to a library solution.
}

تکرار 1

من ابتدا روشی را که Seddon در جیمیل یافت، امتحان کردم. من یک ویژگی جدید به نام 'data-downloadurl' به پیوندهای لنگر فایل ها اضافه کردم. این فرآیند از ویژگی های داده سفارشی HTML5 استفاده می کند. در data-downloadurl باید نوع MIME فایل، نام فایل مقصد (نام فایل مورد نظر فایل دانلود شده) و URL دانلود فایل را وارد کنید. بنابراین، این به قالب HTML اضافه می شود:

<a href="#" class="dnd"
data-downloadurl="{$item.mime}:{$item.filename}:{$item.url}"></a>

که خروجی ای مانند زیر ایجاد می کند:

<a href="#" class="dnd" data-downloadurl=
"image/jpeg:Penguins.jpg:https://www.box.net/box_download_file?file_id=f66690"></a>

بر اساس یک پلاگین جی کوئری که فون شورش ایجاد کرد، که بر اساس مقاله Seddon است، من یک پلاگین جی کوئری اضافه کردم که کمی ویژگی های مرورگر را شناسایی می کند. خطوطی که به نسخه فون شورش اضافه کردم برجسته شده است:

(function($) {

$.fn.extend({
dragout: function() {
var files = this;
if (files.length > 0) {
    $(files).each(function() {
    var url = (this.dataset && this.dataset.downloadurl) ||
                this.getAttribute("data-downloadurl");
    if (this.addEventListener) {
        this.addEventListener("dragstart", function(e) {
        if (e.dataTransfer && e.dataTransfer.constructor == Clipboard &&
            e.dataTransfer.setData('DownloadURL', 'http://www.box.net')) {
            e.dataTransfer.setData("DownloadURL", url);
        }
        },false);
    }
    });
}
}
});

})(jQuery);

دلیل اینکه من این کار را انجام دادم این بود که بدون شناسایی قبلی مرورگر، انجام addEventListener() به یک عنصر HTML در IE یک خطای جاوا اسکریپت ایجاد می کند زیرا IE از متد () attachEvent خود استفاده می کند. e.dataTransfer در IE تعریف نشده است (در حال حاضر)، e.dataTransfer.constructor DataTransfer را در فایرفاکس (Mozilla) برمی گرداند، در حالی که مرورگرهای Webkit (Chrome و Safari) سازنده Clipboard را پیاده سازی می کنند. در Safari، e.dataTransfer.setData('DownloadURL','http://www.box.net') false را برمی گرداند، و Chrome برای این عبارت true برمی گرداند. انجام تمام آزمایش‌های ذکر شده در بالا، این ویژگی را فقط در اختیار کروم قرار می‌دهد. ممکن است استدلال کنید که من به سادگی می توانم کارهای زیر را انجام دهم:

/chrome/.test( navigator.userAgent.toLowerCase() )

اما من تشخیص ویژگی را به تشخیص مرورگر ترجیح می‌دهم، اگرچه این از نظر فنی تشخیص نمی‌دهد که دانلود DnD کار خواهد کرد.

مشکلات تکرار 1

1) از آنجا که ما در حال حاضر DnD روی صفحه را برای جابجایی/کپی فایل ها بین پوشه ها فعال کرده ایم، به راهی برای تشخیص DnD Download و DnD روی صفحه نیاز داریم. از نظر فنی، ما نمی توانیم این دو عمل را با هم ترکیب کنیم. ما نمی توانیم پیش بینی کنیم که آیا کاربر می خواهد فایلی را به پوشه دیگری در حساب Box.net منتقل کند یا آن را به دسکتاپ خود بکشد. این دو عمل کاملاً متفاوت هستند. علاوه بر این، هیچ راه آسانی برای تشخیص قرار گرفتن مکان نما در خارج از پنجره مرورگر وجود ندارد. می توانید از window.onmouseout (IE) و document.onmouseout (سایر مرورگرها) برای پیوست کردن رویداد mouseout به سند استفاده کنید و بررسی کنید که آیا e.relatedTarget.nodeName == "HTML" (e رویداد mouseout یا window.event است، هر کدام که باشد. موجود است). اما این به دلیل حباب رویداد بسیار دشوار است. این رویداد ممکن است زمانی که روی یک تصویر یا لایه قرار دارید، به طور تصادفی فعال شود، به خصوص در یک برنامه وب پیچیده مانند Box.net.

2) ما می خواهیم کاربر به صراحت کاری انجام دهد تا از کشیدن اشتباهی چیزی به دسکتاپ جلوگیری کند. به طور بالقوه، یک ویرایشگر یک پوشه Box می تواند یک فایل اجرایی را آپلود کند که در رایانه هر کسی که آن را دانلود می کند، کار نامطلوبی انجام می دهد. ما می‌خواهیم کاربر بداند دقیقا چه زمانی یک فایل در دسکتاپ دانلود می‌شود.

تکرار 2

ما تصمیم گرفتیم با کنترل + کشیدن (کشیدن یک فایل با فشار دادن کلید Ctrl ویندوز) آزمایش کنیم. این عمل با کاری که افراد می توانند روی دسکتاپ ویندوز برای کپی کردن یک فایل انجام دهند مطابقت دارد. همچنین برای جلوگیری از دانلود اشتباه فایل‌ها، به کار اضافی (اما نه یک مرحله اضافی) از کاربر نیاز دارد.

افزونه jQuery در تکرار 1 اکنون کنار گذاشته شده است زیرا ما باید DnD Download را به شدت با DnD موجود در صفحه ادغام کنیم. برای کسانی که علاقه مند هستند، از نسخه اصلاح شده افزونه Draggable jQuery UI استفاده می کنیم. در داخل رویداد mousedown یک عنصر هدف، کد زیر را قرار می دهیم:

// DnD to desktop when the Ctrl key is pressed while dragging
if (e.ctrlKey) {
var that = $(e.target);
// make sure it is not IE (attachEvent).
if (that[0].addEventListener) {
    that[0].addEventListener("dragstart",function(e) {
        // e.dataTransfer in Firefox uses the DataTransfer constructor
        // instead of Clipboard
        // make sure it's Chrome and not Safari (both webkit-based).
        // setData on DownloadURL returns true on Chrome, and false on Safari
        if (e.dataTransfer && e.dataTransfer.constructor == Clipboard &&
            e.dataTransfer.setData('DownloadURL','http://www.box.net')) {
        var url = (this.dataset && this.dataset.downloadurl) ||
                    this.getAttribute("data-downloadurl");
        e.dataTransfer.setData("DownloadURL", url);
        }
    }, false);
    return;
}
}

به غیر از فعال کردن کلید Ctrl، ما یک راهنمای ابزار توستر کوچک نیز اضافه کرده‌ایم که وقتی کاربر یک کشیدن معمولی روی صفحه را انجام می‌دهد، نشان داده می‌شود. این به کاربر می گوید که اگر نماد فایل در حالی که کلید Ctrl نگه داشته شده است به دسکتاپ کشیده شود، فایل ها قابل دانلود هستند.

مشکلات تکرار 2

به دلیل نگرانی‌های امنیتی، Box.net آدرس‌های اینترنتی دائمی را برای دسترسی مستقیم به فایل‌های استاتیک در معرض نمایش نمی‌گذارد. این مختص Box.net نیست. هیچ سرویس ذخیره سازی آنلاین نباید URL های دائمی را بدون لایه امنیتی اضافی برای بررسی عمومی بودن فایل و اینکه آیا دانلود مورد نظر توسط کاربر با مجوزهای مناسب درخواست شده است، در معرض نمایش قرار دهد.

هنگام دنبال کردن "URL دانلود" (به عنوان مثال https://www.box.net/box_download_file?file_id=f_60466690 ) یک مورد، یک کد وضعیت "302 Found" را برمی گرداند و به یک URL تصادفی هدایت می شود (مثلا https://www.box.net/dl/6045?a=1f1207a084&m=168299,11211&t=2&b=aca15820d924e3b ) که "نشانی اینترنتی واقعی" موقت فایل است. چالش این است که هر چند دقیقه یکبار منقضی می شود و بنابراین قرار دادن آن در خروجی HTML غیر عملی است. زمانی که کاربر سعی می کند فایل را از لینک موجود در خروجی HTML که چند دقیقه پیش ایجاد شده است دانلود کند، می تواند "404" را برگرداند.

DnD Download فقط روی URL های واقعی که مستقیماً به یک منبع اشاره می کنند کار می کند. اگر تغییر مسیر درگیر باشد، در حال حاضر به اندازه کافی هوشمند نیست که زنجیره را دنبال کند (و هرگز نباید زنجیره را به دلیل امنیت دنبال کند). بنابراین، اگرچه پیوند https://www.box.net/box_download_file?file_id=f_60466690 از بالا به شما امکان می دهد فایل را هنگام وارد کردن آن در نوار مکان مرورگر بارگیری کنید، اما با DnD کار نمی کند.

برای نشان دادن بهتر تفاوت‌های بین «URL واقعی» و «URL تغییر مسیر»، تصاویر را ببینید:

URL تغییر مسیر 302
URL تغییر مسیر 302
URL واقعی
URL واقعی

تکرار 3

بیایید آژاکس را امتحان کنیم.

ما کد را در تکرار قبلی کمی تغییر دادیم و به موارد زیر رسیدیم:

// DnD to desktop when the Ctrl key is pressed while dragging
if (e.ctrlKey) {
var that = $(e.target);
// make sure it is not IE (attachEvent).
if (that[0].addEventListener) {
that[0].addEventListener("dragstart", function(e) {
    // e.dataTransfer in Firefox uses the DataTransfer constructor
    // instead of Clipboard
    // make sure it's Chrome and not Safari (both webkit-based).
    // setData on DownloadURL returns true on Chrome, and false on Safari
    if (e.dataTransfer && e.dataTransfer.constructor == Clipboard &&
        e.dataTransfer.setData('DownloadURL', 'http://www.box.net')) {
    var url = (this.dataset && this.dataset.downloadurl) ||
                this.getAttribute("data-downloadurl");
    $.ajax({
        complete: function(data) {
        e.dataTransfer.setData("DownloadURL", data.responseText);
        },
        type:'GET',
        url: url
    });
    }
}, false);
return;
}
}

این منطقی است. پس از درگ استارت، فوراً یک تماس Ajax با سرور برقرار می کند تا آخرین URL دانلود فایل را بازیابی کند. با این حال، کار نمی کند.

به نظر می رسد که باید یک تماس همزمان باشد (یا همانطور که من دوست دارم آن را Sjax بنامم). به نظر می رسد setData باید در زمانی که شنونده رویداد متصل است انجام شود. با توجه به API جی کوئری، خطوط برجسته شده به صورت زیر هستند:

$.ajax({
async: false,
complete: function(data) {
e.dataTransfer.setData("DownloadURL", data.responseText);
},
type: 'GET',
url: url
});

و تا زمانی که اتصال شبکه را قطع نکردم خوب کار می کند. از آنجایی که یک تماس همزمان انجام می دهد، مرورگر تا زمانی که تماس با موفقیت انجام شود متوقف می شود. اگر تماس Ajax ناموفق باشد (404، یا اگر اصلاً پاسخ ندهد)، مرورگر به هیچ وجه یخ‌زدایی نمی‌کند، انگار که از کار افتاده است.

انجام کاری مانند موارد زیر بسیار ایمن تر است:

$.ajax({
async: false,
complete: function(data) {
e.dataTransfer.setData("DownloadURL", data.responseText);
},
error: function(xhr) {
if (xhr.status == 404) {
    xhr.abort();
}
},
type: 'GET',
timeout: 3000,
url: url
});

برای نمایش این ویژگی، می‌توانید یک فایل ثابت را در حساب Box.net آپلود کنید. در حالی که کلید Ctrl را نگه داشته اید، نماد فایل را به سمت دسکتاپ خود بکشید. اگر حساب کاربری ندارید، ایجاد آن به معنای واقعی کلمه کمتر از 30 ثانیه طول می کشد.

با این ویژگی می توانید خلاق باشید و خیلی چیزها را ممکن کنید. با کشیدن یک تصویر به دیالوگ چاپگر ویندوز، تصویر بلافاصله چاپ می شود. می‌توانید یک آهنگ را از Box در درایو تلفن همراه خود کپی کنید، یک فایل را از Box به مشتری IM خود بکشید تا مستقیماً آن را به دوست خود منتقل کنید... این فرصت‌های بی‌پایانی را برای افزایش بهره‌وری شما باز می‌کند.

پاره کردن یک فایل به چاپگر
کشیدن فایل به چاپگر
کشیدن یک فایل به سرویس گیرنده IM
کشیدن یک فایل به سرویس گیرنده IM.

افکار و پیشرفت های آینده

این هنوز ایده آل نیست، زیرا یک تماس همزمان می تواند مرورگر را برای لحظه ای کوتاه قفل کند. Web Worker HTML 5 نیز کمکی نمی کند، زیرا Web Worker باید ناهمزمان باشد. به نظر می رسد که setData باید در زمانی که شنونده رویداد متصل است انجام شود.

در واقعیت، عملکرد بسیار قابل قبول است. فراخوانی همزمان آژاکس (Sjax) صرفاً یک رشته URL را بازیابی می کند که باید بسیار سریع باشد. سربار بزرگی در هدر HTTP دارد که احتمالاً می‌تواند توسط WebSockets برطرف شود. با این حال، تا زمانی که شاهد استفاده بیشتر از این نوع فناوری نباشیم، ارزش استفاده از WebSockets برای ارسال هر به‌روزرسانی کوچک برای مشتری را ندارد.

همچنین امیدوارم در آینده قابلیت دانلود چند فایل به API اضافه شود. همراه با چک باکس های سفارشی برای انتخاب چندین فایل در رابط کاربری، این امر باورنکردنی خواهد بود. علاوه بر این، اگر فایل‌های تولید شده توسط مشتری، مانند فایل‌های متنی تولید شده از نتیجه فرم ارسال شده، به این روش قابل دانلود باشند، حتی بهتر خواهد بود.

  • ستون dnd
  • تنظیم مجدد لیست
  • ایجاد گالری تصاویر
  • صادرات یک تصویر بوم

منابع