บทนำ
การลากและวาง (DnD) เป็นหนึ่งในฟีเจอร์ที่ยอดเยี่ยมมากมายของ HTML 5 และได้รับการรองรับใน Firefox 3.5, Safari, Chrome และ IE เมื่อเร็วๆ นี้ Google ได้เปิดตัวฟีเจอร์ใหม่ที่ช่วยให้ผู้ใช้ Google Chrome ลากและวางไฟล์จากเบราว์เซอร์ไปยังเดสก์ท็อปได้ ฟีเจอร์นี้สะดวกมาก แต่ไม่ค่อยมีคนรู้จักจนกระทั่ง Ryan Seddon โพสต์บทความเกี่ยวกับการค้นพบจากการถอดรหัสฟีเจอร์ใหม่นี้
เราที่ Box.net รู้สึกตื่นเต้นมากที่ความสามารถใหม่ๆ เหล่านี้ช่วยให้เราปรับปรุงโซลูชันการจัดการเนื้อหาบนระบบคลาวด์ รวมถึงมีส่วนร่วมกับชุมชนนักพัฒนาซอฟต์แวร์ได้มากขึ้น เรายินดีที่จะแจ้งให้ทราบว่า DnD Download ผสานรวมกับผลิตภัณฑ์ของเราแล้ว ตอนนี้ผู้ใช้ Box สามารถลากไฟล์จากเบราว์เซอร์ Chrome ไปยังเดสก์ท็อปได้โดยตรงเพื่อดาวน์โหลดและบันทึกไฟล์
เราขอแชร์กระบวนการพัฒนาฟีเจอร์ใหม่นี้ซึ่งผ่านขั้นตอนซ้ำๆ หลายครั้ง
ตรวจสอบการรองรับ API การลากและวาง
สิ่งแรกที่ต้องทำคือตรวจสอบว่าเบราว์เซอร์ของคุณรองรับการลากและวาง HTML5 อย่างเต็มรูปแบบ วิธีง่ายๆ ในการทำเช่นนี้คือใช้ไลบรารีชื่อ Modernizr เพื่อตรวจสอบฟีเจอร์บางอย่าง ดังนี้
if (Modernizr.draganddrop) {
// Browser supports native HTML5 DnD.
} else {
// Fallback to a library solution.
}
Iteration 1
ก่อนอื่น เราลองใช้แนวทางที่ Seddon พบใน Gmail เราได้เพิ่มแอตทริบิวต์ใหม่ที่มีชื่อว่า "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>
เราเพิ่มplugin jQuery ที่ตรวจหาฟีเจอร์เบราว์เซอร์เล็กน้อยโดยอิงตามบทความของ Seddon และปลั๊กอิน jQuery ที่ von Schorsch สร้างขึ้น บรรทัดที่ไฮไลต์คือบรรทัดที่ฉันเพิ่มลงในเวอร์ชันของ von Schorsch
(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 จะทำให้เกิดข้อผิดพลาด JavaScript เนื่องจาก IE ใช้เมธอด attachEvent() ของตัวเอง
e.dataTransfer ไม่มีค่าใน IE (ณ ตอนนี้) ส่วน e.dataTransfer.constructor จะแสดงผล DataTransfer ใน Firefox (Mozilla) ส่วนเบราว์เซอร์ Webkit (Chrome และ Safari) จะใช้คอนสตรัคเตอร์คลิปบอร์ด
ใน Safari e.dataTransfer.setData('DownloadURL','http://www.box.net')
จะแสดงผลเป็นเท็จ และ Chrome จะแสดงผลเป็นจริงสำหรับคำสั่งนี้ การทดสอบทั้งหมดที่กล่าวถึงข้างต้นจะทำให้ฟีเจอร์นี้พร้อมใช้งานใน Chrome เท่านั้น
คุณอาจโต้แย้งว่าฉันแค่ทำสิ่งต่อไปนี้ได้
/chrome/.test( navigator.userAgent.toLowerCase() )
แต่เราชอบการตรวจหาฟีเจอร์มากกว่าการตรวจหาเบราว์เซอร์ แม้ว่าในทางเทคนิคแล้ววิธีนี้จะไม่ตรวจพบว่าการดาวน์โหลด DnD จะใช้งานได้
ปัญหาของการทำซ้ำ 1
1) เนื่องจากปัจจุบันเราเปิดใช้ DnD ในหน้าเว็บสำหรับการย้าย/คัดลอกไฟล์ระหว่างโฟลเดอร์ เราจึงต้องมีวิธีแยกความแตกต่างระหว่าง DnD การดาวน์โหลดกับ DnD ในหน้าเว็บ ในทางเทคนิคแล้ว เราไม่สามารถรวมการดำเนินการทั้ง 2 รายการนี้เข้าด้วยกัน เราไม่สามารถคาดเดาได้ว่าผู้ใช้ต้องการย้ายไฟล์ไปยังโฟลเดอร์อื่นภายในบัญชี Box.net หรือลากไปยังเดสก์ท็อป การดำเนินการทั้ง 2 อย่างนี้แตกต่างกันโดยสิ้นเชิง
นอกจากนี้ ยังไม่มีวิธีง่ายๆ ในการตรวจจับว่าเคอร์เซอร์อยู่นอกหน้าต่างเบราว์เซอร์หรือไม่
คุณสามารถใช้ window.onmouseout (IE) และ document.onmouseout (เบราว์เซอร์อื่นๆ) เพื่อแนบเหตุการณ์ mouseout กับเอกสาร และตรวจสอบว่า e.relatedTarget.nodeName == "HTML"
(e คือเหตุการณ์ mouseout หรือ window.event แล้วแต่ว่าจะใช้อันไหนได้) แต่วิธีนี้ค่อนข้างยากเนื่องจากมีการส่งผ่านเหตุการณ์
เหตุการณ์อาจทริกเกอร์แบบสุ่มเมื่อคุณอยู่เหนือรูปภาพหรือเลเยอร์ โดยเฉพาะอย่างยิ่งในเว็บแอปที่ซับซ้อน เช่น Box.net
2) เราต้องการให้ผู้ใช้ดำเนินการอย่างชัดแจ้งเพื่อป้องกันไม่ให้ลากสิ่งต่างๆ ไปยังเดสก์ท็อปโดยไม่ตั้งใจ เอดิเตอร์ของโฟลเดอร์ Box อาจอัปโหลดไฟล์ปฏิบัติการที่ทําสิ่งไม่พึงประสงค์ในคอมพิวเตอร์ของผู้ที่ดาวน์โหลดไฟล์ได้ เราต้องการให้ผู้ใช้ทราบเวลาที่ระบบจะดาวน์โหลดไฟล์ไปยังเดสก์ท็อป
Iteration 2
เราจึงตัดสินใจทดสอบด้วยแป้น Ctrl + ลาก (การลากไฟล์เมื่อกดแป้น Ctrl ของ Windows) การดำเนินการนี้สอดคล้องกับสิ่งที่ผู้ใช้สามารถทำได้บนเดสก์ท็อป Windows เพื่อทำสำเนาไฟล์ นอกจากนี้ ผู้ใช้ยังต้องดำเนินการเพิ่มเติม (แต่ไม่ใช่ขั้นตอนเพิ่มเติม) เพื่อป้องกันไม่ให้ดาวน์โหลดไฟล์โดยไม่ตั้งใจ
ตอนนี้เราเลิกใช้ปลั๊กอิน jQuery ในรุ่นที่ 1 แล้ว เนื่องจากเราต้องผสานรวมการดาวน์โหลด DnD กับ DnD ในหน้าเว็บอย่างแน่นหนา สําหรับผู้ที่สนใจ เราใช้ปลั๊กอินที่ลากได้ของ 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 ไม่แสดง URL แบบถาวรเพื่อเข้าถึงไฟล์แบบคงที่โดยตรงเนื่องจากข้อกังวลด้านความปลอดภัย ปัญหานี้ไม่ได้เกิดขึ้นเฉพาะกับ 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
) ซึ่งเป็น "URL จริง" ชั่วคราวของไฟล์ ปัญหาคือรหัสจะหมดอายุทุก 2-3 นาที ดังนั้นการวางรหัสไว้ในเอาต์พุต HTML จึงใช้ไม่ได้จริง ระบบอาจแสดงผลเป็น "404" เมื่อผู้ใช้พยายามดาวน์โหลดไฟล์จากลิงก์ในเอาต์พุต HTML ที่สร้างขึ้นเมื่อหลายนาทีที่ผ่านมา
การดาวน์โหลด DnD จะใช้งานได้กับ URL จริงที่ชี้ไปยังทรัพยากรโดยตรงเท่านั้น หากมีการเปลี่ยนเส้นทาง ขณะนี้เครื่องมือนี้ยังไม่ฉลาดพอที่จะติดตามเชน (และไม่ควรติดตามเชนเนื่องจากเหตุผลด้านความปลอดภัย) ดังนั้น แม้ว่าลิงก์ https://www.box.net/box_download_file?file_id=f_60466690 จากด้านบนจะช่วยให้คุณดาวน์โหลดไฟล์ได้เมื่อป้อนลิงก์ในแถบที่อยู่ของเบราว์เซอร์ แต่จะใช้กับ DnD ไม่ได้
ภาพหน้าจอต่อไปนี้แสดงความแตกต่างระหว่าง "URL จริง" กับ "URL เปลี่ยนเส้นทาง" ได้ดียิ่งขึ้น
Iteration 3
มาลองใช้ Ajax กัน
เราได้แก้ไขโค้ดในการวนซ้ำก่อนหน้านี้เล็กน้อยและได้โค้ดต่อไปนี้
// 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 ของ jQuery
$.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 วินาที
ฟีเจอร์นี้ช่วยให้คุณสร้างสรรค์และทําสิ่งต่างๆ ได้มากมาย การลากรูปภาพไปยังกล่องโต้ตอบเครื่องพิมพ์ของ Windows จะเป็นการพิมพ์รูปภาพทันที คุณสามารถคัดลอกเพลงจาก Box ไปยังไดรฟ์ของโทรศัพท์มือถือ ลากไฟล์จาก Box ไปยังโปรแกรมรับส่งข้อความเพื่อให้โอนไปยังเพื่อนได้โดยตรง… ซึ่งเปิดโอกาสให้คุณเพิ่มประสิทธิภาพการทำงานได้ไม่รู้จบ
ความคิดเห็นและการปรับปรุงในอนาคต
ซึ่งยังไม่ใช่วิธีที่ดีที่สุด เนื่องจากการเรียกใช้แบบซิงค์อาจทำให้เบราว์เซอร์ค้างเป็นระยะเวลาสั้นๆ Web Worker ของ HTML 5 ก็ไม่ช่วยเช่นกัน เนื่องจาก Web Worker ต้องเป็นแบบไม่พร้อมกัน ดูเหมือนว่า setData จะต้องดำเนินการในขณะที่แนบโปรแกรมรับฟังเหตุการณ์
แต่ประสิทธิภาพนั้นยอมรับได้ การเรียกใช้ AJAX แบบซิงค์ (Sjax) จะดึงข้อมูลสตริง URL เท่านั้น ซึ่งควรรวดเร็วมาก แต่ก็มีค่าใช้จ่ายเพิ่มเติมมากในส่วนหัว HTTP ซึ่ง WebSocket อาจช่วยแก้ปัญหานี้ได้ อย่างไรก็ตาม เราไม่แนะนำให้ใช้ WebSockets เพื่อส่งการอัปเดตเล็กๆ น้อยๆ ไปยังไคลเอ็นต์จนกว่าเราจะเห็นการใช้งานเทคโนโลยีประเภทนี้มากขึ้น
นอกจากนี้ เราหวังว่าจะมีการเพิ่มความสามารถในการดาวน์โหลดไฟล์หลายรายการลงใน API ในอนาคต เมื่อรวมกับช่องทำเครื่องหมายที่กำหนดเองเพื่อเลือกไฟล์หลายรายการในอินเทอร์เฟซผู้ใช้ ฟีเจอร์นี้จะยอดเยี่ยมมาก นอกจากนี้ การดาวน์โหลดไฟล์ที่ลูกค้าสร้างขึ้น เช่น ไฟล์ข้อความที่สร้างขึ้นจากผลลัพธ์ของแบบฟอร์มที่ส่งมา จะเป็นวิธีที่ยอดเยี่ยมยิ่งขึ้น
- ลากและวางคอลัมน์
- จัดเรียงรายการใหม่
- การสร้างแกลเลอรีรูปภาพ
- การส่งออกรูปภาพแคนวาส
ข้อมูลอ้างอิง
- ข้อกำหนดการลากและวาง