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):
"use strict";
var unscheduledTbody = null,
scheduledTbody = null,
dataTransferValue = null,
isDroppable = true;
var getIndicesOf = function (td) {
var tr = td.parent(),
tbody = tr.parent();
var row = tbody.children().index(tr),
column = tr.children().index(td);
return [row, column];
};
var verifyRowspan = function (options) {
var tdIndicies = getIndicesOf(options.td),
selectors = "",
elements = null,
element = null,
i = 0;
for (; i < options.rows; i++) {
selectors = (selectors + ("tr:eq(" + (tdIndicies[0] + i) + ") td." + tdIndicies[1] + ","));
}
selectors = selectors.substring(0, (selectors.length - 1));
elements = scheduledTbody.find(selectors);
for (i = 0; i < elements.length; i++) {
element = elements[i];
if (element.id !== "") {
return false;
}
}
tdIndicies = null;
selectors = null;
elements = null;
element = null;
i = null;
return true;
};
var toggleVisibility = function (options) {
var tdIndicies = getIndicesOf(options.td),
selectors = "",
i = 1;
for (; i < options.rows; i++) {
if (options.hide) {
selectors = (selectors + ("tr:eq(" + (tdIndicies[0] + i) + ") td." + tdIndicies[1] + ","));
} else {
selectors = (selectors + ("tr:eq(" + (tdIndicies[0] + i) + ") td." + tdIndicies[1] + ":hidden,"));
}
}
selectors = selectors.substring(0, (selectors.length - 1));
if (selectors.length > 0) {
scheduledTbody.find(selectors).css({
display: (options.hide ? "none" : "table-cell")
});
}
options = null;
tdIndicies = null;
i = null;
};
var dragEnterHandler = function (e) {
dataTransferValue = e.originalEvent.dataTransfer.getData("text/plain");
var source = $("#" + dataTransferValue),
target = $(this),
rows = source.data("rows");
isDroppable = verifyRowspan({
td: target,
rows: rows
});
if (isDroppable) {
e.originalEvent.dataTransfer.dropEffect = "move";
$(this).addClass("Droppable");
} else {
$(this).addClass("NotDroppable");
}
source = null;
target = null;
rows = null;
};
var dropHandler = function (e) {
e.preventDefault();
var target = $(this);
if (isDroppable) {
var source = $("#" + dataTransferValue),
rows = source.data("rows"),
url = source.data("url");
target.removeClass("Droppable")
.removeClass("NotDroppable")
.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");
toggleVisibility({
td: target,
rows: rows,
hide: true
});
source = null;
rows = null;
url = 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").removeClass("NotDroppable");
}
target = null;
};
$(function () {
if (console.clear) {
console.clear();
}
unscheduledTbody = $("#Unscheduled tbody");
scheduledTbody = $("#Scheduled tbody");
$("#Unscheduled tbody,#Scheduled tbody").on({
dragstart:function (e) {
e.originalEvent.dataTransfer.effectAllowed = "move";
e.originalEvent.dataTransfer.setData("text/plain", this.id);
},
dragend: function (e) {
switch (e.originalEvent.dataTransfer.dropEffect) {
case "copy":
case "move":
if (isDroppable) {
var source = $(this),
target = $("#" + dataTransferValue);
if (source.data("scheduled")) {
var rows = target.data("rows");
toggleVisibility({
td: source,
rows: rows,
hide: false
});
rows = null;
} else {
source.parent().remove();
}
dataTransferValue = null;
isDroppable = true;
source = null;
target = null;
}
break;
case "link":
case "none":
break;
default:
break;
}
}
}, "[draggable]");
scheduledTbody.on({
dragenter: dragEnterHandler,
dragover: function (e) {
e.preventDefault();
},
dragleave: function (e) {
$(this)
.removeClass("Droppable")
.removeClass("NotDroppable");
},
drop: dropHandler
}, "td").on("click", "a", function (e) {
e.preventDefault();
var source = $(this).closest("td"),
rows = source.data("rows"),
url = source.data("url");
unscheduledTbody
.append($("<tr />")
.append($("<td draggable='true' data-scheduled='false' />")
.html(source.html())
.attr("id", source.attr("id"))
.data("rows", rows)
.data("url", url)));
source.html("")
.removeAttr("id")
.removeAttr("rowspan")
.removeData("rows")
.removeData("url");
toggleVisibility({
td: source,
rows: rows,
hide: false
});
source = null;
rows = null;
url = null;
});
});
UPDATE
I spent most of the day today trying to get @ANeves' suggestions implemented.
- I've replaced any double quote escaping.
- I've aded
use strict;. - I've replaced the
==comparer with===. - I renamed
IndeciestogetIndicesOf(I opted to keep it as indices instead of indexes because it just didn't sound right to me). - All functions were renamed and start with lower case letters.
- Converted everything from using
.live()to.on(), which was an interesting experience. Thanks for pointing me to.on()@ANeves, I didn't even know about its existence.
I've also shuffled a huge chunk of code out into reusable functions, especially the ones that deal with manipulating the cells.
I tried not to pollute the global namespace, but I just couldn't find a way around it. If someone has suggestions on getting that done, please by all means let me know.
As it stands, the prototype is mostly usable. One bug I can't seem to get a grip on is if I want to move a cell right above itself. For example, if you look at the JSFiddle, if you place Item D anywhere and then try to move it once cell up, it hits the verifyRowspan check and cancels out because it conflicts with itself. I tried getting around it by also checking if one of the cells in it's path has the id that it does, but it doesn't work very reliably. I'd appreciate suggestions on this.
And lastly, if any of you see anything else that's wrong or could be improved, please let me know.
Thanks!