I've just finished a week long adventure of creating an HTML5 Drag and Drop scheduler. As the prototype stands it works fine, but I'm curious if some of the code can be optimized. I mostly focused on it being readable and well documented, but I'm curious if someone can come up with any optimizations.
I've noticed that when Firefox is used there is some "twitching" that occurs when the cells are updated. Here's the code (minus the comments that are available on the fiddle):
var unscheduledTbody = null,
scheduledTbody = null,
isDroppable = true;
var dataTransferValue = null;
var Indecies = function (td) {
var tr = td.parent(),
tbody = tr.parent();
var row = tbody.children().index(tr),
column = tr.children().index(td);
return [row, column];
};
var CheckCellRowspan = function (options) {
var element = null;
for (var i = 1; i < options.rows; i++) {
element = scheduledTbody.find("tr:eq(" + (options.row + i) + ") td." + options.column);
if (element.attr("id") != null) {
isDroppable = false;
return;
};
};
element = null;
isDroppable = true;
};
var ToggleCellVisibility = function (options) {
var selectors = "",
i = 1;
for (i; i < options.rows; i++) {
if (options.hide) {
selectors = (selectors + ("tr:eq(" + (options.row + i) + ") td." + options.column + ","));
} else {
selectors = (selectors + ("tr:eq(" + (options.row + i) + ") td." + options.column + ":hidden,"));
};
};
selectors = selectors.substring(0, (selectors.length - 1));
if (selectors.length > 0) {
scheduledTbody.find(selectors).css({
display: (options.hide ? "none" : "table-cell")
});
};
};
$(function () {
$("#Unscheduled tbody td,#Scheduled tbody td").prop("draggable", true);
unscheduledTbody = $("#Unscheduled tbody");
scheduledTbody = $("#Scheduled tbody");
unscheduledTbody.find("td").data("scheduled", false);
var scheduledTds = scheduledTbody.find("td");
$("[draggable]").live("dragstart", function (e) {
e.originalEvent.dataTransfer.effectAllowed = "move";
e.originalEvent.dataTransfer.setData("text/plain", this.id);
}).live("drag", function (e) {
}).live("dragend", function (e) {
var dropEffect = e.originalEvent.dataTransfer.dropEffect;
switch (dropEffect) {
case "copy":
case "move":
if (isDroppable) {
var source = $(this),
target = $("#" + dataTransferValue);
if (source.data("scheduled")) {
var sourceIndecies = Indecies(source),
sourceRow = sourceIndecies[0],
sourceColumn = sourceIndecies[1],
rows = target.data("rows");
ToggleCellVisibility({
rows: rows,
row: sourceRow,
column: sourceColumn,
hide: false
});
sourceIndecies = null;
sourceRow = null;
sourceColumn = null;
rows = null;
} else {
source.parent().remove();
};
dataTransferValue = null;
source = null;
target = null;
};
break;
case "link":
case "none":
default:
break;
};
});
scheduledTds.each(function () {
var td = $(this);
td.addClass("" + Indecies(td)[1]);
}).data("scheduled", true).live("dragenter", function (e) {
e.originalEvent.dataTransfer.dropEffect = "move";
$(this).addClass("Droppable");
}).live("dragover", function (e) {
e.preventDefault();
}).live("dragleave", function (e) {
$(this).removeClass("Droppable");
}).live("drop", function (e) {
e.preventDefault();
dataTransferValue = e.originalEvent.dataTransfer.getData("text/plain");
var source = $("#" + dataTransferValue),
target = $(this),
targetIndecies = Indecies(target),
targetRow = targetIndecies[0],
targetColumn = targetIndecies[1],
rows = source.data("rows");
CheckCellRowspan({
rows: rows,
row: targetRow,
column: targetColumn
});
if (isDroppable) {
var sourceIndecies = Indecies(source),
sourceRow = sourceIndecies[0],
sourceColumn = sourceIndecies[1],
url = source.data("url");
target.removeClass("Droppable")
.html(source.html())
.attr("id", source.attr("id"))
.attr("rowspan", rows)
.data("rows", rows)
.data("url", url);
source.html("")
.removeAttr("id")
.removeAttr("rowspan")
.removeData("rows")
.removeData("url");
ToggleCellVisibility({
rows: rows,
row: targetRow,
column: targetColumn,
hide: true
});
sourceIndecies = null;
sourceRow = null;
sourceColumn = null;
} else {
$("b").text("The cell could not be dropped at the target location. It conflicted with an existing cell in its path.");
target.removeClass("Droppable");
};
source = null;
target = null;
targetIndecies = null;
targetRow = null;
targetColumn = null;
rows = null;
}).find("a").live("click", function (e) {
e.preventDefault();
var source = $(this).closest("td"),
sourceIndecies = Indecies(source),
sourceRow = sourceIndecies[0],
sourceColumn = sourceIndecies[1],
rows = source.data("rows");
unscheduledTbody
.append($("<tr />")
.append($("<td draggable=\"true\" data-scheduled=\"false\" />")
.html(source.html())
.attr("id", source.attr("id"))
.data("rows", rows)));
source.html("")
.removeAttr("id")
.removeAttr("rowspan")
.removeData("rows");
ToggleCellVisibility({
rows: rows,
row: sourceRow,
column: sourceColumn,
hide: false
});
source = null;
sourceIndecies = null;
sourceRow = null;
sourceColumn = null;
rows = null;
});
scheduledTds = null;
});