Wednesday, October 14, 2009

 

jQuery, how do I love thee?

So I spent a few days doing some JavaScript, which is not my favourite thing. Or at least it didn't use to be. But now that I have discovered JQuery, I gotta say, it's a whole lot more tolerable than it used to be.

JQuery makes some things that I would previous have considered all but impossible downright easy. Take drag-and-drop inside the browser, f'rinstance. To do it yourself, you'd have to deal with, jeez, I don't even know what, DOM craziness, layers, who knows. In jQuery? Well, lemme give you a quick example, stripped of all the actual business logic, of a drag-and-drop form builder:


<html><head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.js"></script>

<style type="text/css">
.columns:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
* html .columns {height: 1%;}
.columns{ display:inline-block; }
.columns{ display:block; }
.columns .column{
float:left;
display:inline;
min-height:360px;
}
.columns .last{ float:right; }
.columns .first{ width:180px; background-color:#ffeeee; }
.columns .second{ width:280px; margin-left:10px; background-color:#eeffee; }
.columns .last{ width:210px; background-color:#eeeeff}

.footers{ display:inline-block; }
.footers{ display:block; }
.footers .footer{
float:left;
display:inline;
}
.footers .last{ float:right; }
.footers .first{ width:180px; background-color:#eeeeee; }
.footers .second{ width:280px; margin-left:10px; background-color:#eeeeee; }
.footers .last{ width:210px; background-color:#eeeeee}

.formLineEdit { text-align: right; }
.optionLabel { float:right; text-align: right; }
.label { font-weight: bold; vertical-align: text-top; }
.formInput { text-align: right; vertical-align: text-top; }
.editHeader { text-align: right; }
.selectOption { text-align: right; }
#error { text-align: center; color: #ff0000; }
#success { text-align: center; color: #00ff00; }
#listForms { float: left; text-align: left; }
#viewHelp { float: right; text-align: right; }
#inputOptions { text-align: right; }
#detailHeader { text-align: right; }
#formName {background-color: #eeeeee; }
#formVersionNumber {background-color: #eeeeee; }
#formSubmitButton {text-align:right; background-color: #eeeeee; }
</style>

<script>
var totalItems=0;
var currentlyEditing=null;
var lastEdited=null;

$(function() {
//make divs created out of XML clickable
var destClicked=false;
$("#destination").mousedown( function() {
if (!destClicked) { //only do this once, or it might get messy
$("#destination").children().each( function() {
$(this).bind("click", function() {
showEditDetailsFor($(this));
});
});
}
destClicked=true;
});

//set up drag-and-drop stuff
$("#textInput").draggable(
{ connectToSortable:'#destination',
cursor:'move',
helper:'clone',
}
);
$("#longText").draggable(
{ connectToSortable:'#destination',
cursor:'move',
helper:'clone',
}
);
$("#selectMultiple").draggable(
{ connectToSortable:'#destination',
cursor:'move',
helper:'clone',
}
);
$("#selectOne").draggable(
{ connectToSortable:'#destination',
cursor:'move',
helper:'clone',
}
);
$("#destination").sortable(
{
change: function(event, ui) {
ui.placeholder.css({visibility: 'visible', border : '2px solid yellow'});
},
start: function(event, ui) {
ui.placeholder.css({visibility: 'visible', border : '2px solid yellow'});
var tempID=ui.item.attr("id");
if (tempID.indexOf("_")==-1) {
tempID=tempID+"_"+totalItems++;
ui.item.attr({id:tempID});
}
},
stop: function(event, ui) {
//load element details, and ensure they'll show up again when this item is clicked
showEditDetailsFor(ui.item);
ui.item.bind("click", function() {
showEditDetailsFor(ui.item);
});
},
}
);
});

function showEditDetailsFor ( object ) {
// object.effect("highlight", {}, 3000);
currentlyEditing=object.attr("id");
if (currentlyEditing==lastEdited)
return;

$(".formLineEdit").hide();
$("#inputOptions").show();

if (object.attr("id").indexOf("textInput")==0) {
var inputLabel = $.trim(object.find(".label").text());
var inputID = object.find("input[name=inputID]").val();
if (inputLabel!="Text Input" || object.find("input[name=inputID]").attr("name")!="inputID") {
$("#textInputEdit").find("input[name=textInputLabel]").val(inputLabel);
$("#textInputEdit").find("input[name=textInputValue]").val(inputID);
}
$("#textInputEdit").show();
}
else if (object.attr("id").indexOf("longText")==0) {
var inputLabel = $.trim(object.find(".label").text());
var inputID = object.find("textarea:first").val();
if (inputLabel!="Long Text" || inputID!="") {
$("#longTextEdit").find("input[name=longTextLabel]").val(inputLabel);
$("#longTextEdit").find("input[name=longTextValue]").val(inputID);
}
$("#longTextEdit").show();
}
else if (object.attr("id").indexOf("selectMultiple")==0) {
var inputLabel = $.trim(object.find(".label:first").text());
var inputID = object.find("input:last").attr("name");
if (inputLabel!="Select Multiple" || inputID!="selectMulti") {
$("#selectMultiEdit").find("input[name=selectMultiLabel]").val(inputLabel);
$("#selectMultiEdit").find("input[name=selectMultiValue]").val(inputID);
blankOption=$("#selectMultiEdit > .selectOption").remove();
var addedChild=false;
object.children(".formInput").each( function() {
optionClone=blankOption.clone();
var optionLabel=$(this).find(".optionLabel").text();
optionClone.find("input[name=multiOptionLabel]").val(optionLabel);
var optionValue=$(this).find("input:first").attr("value");
optionClone.find("input[name=multiOptionValue]").val(optionValue);
cloneRemoveButton = optionClone.find("input[name=removeSelectMultiOption]");
cloneRemoveButton.bind("mouseup", function() {
$(this).parent().remove();
});
$("#selectMultiEdit").append(optionClone);
addedChild=true;
});
if (!addedChild)
$("#selectMultiEdit").append(blankOption);
} //don't show defaults
$("#selectMultiEdit").show();
}
else if (object.attr("id").indexOf("selectOne")==0) {
var inputLabel = $.trim(object.find(".label:first").text());
var inputID = object.find("input:last").attr("name");
if (inputLabel!="Select One" || inputID!="selectOne") {
$("#selectOneEdit").find("input[name=selectOneLabel]").val(inputLabel);
$("#selectOneEdit").find("input[name=selectOneValue]").val(inputID);
blankOption=$("#selectOneEdit > .selectOption").remove();
var addedChild=false;
object.children(".formInput").each( function() {
optionClone=blankOption.clone();
var optionLabel=$(this).find(".optionLabel").text();
optionClone.find("input[name=oneOptionLabel]").val(optionLabel);
var optionValue=$(this).find("input:first").attr("value");
optionClone.find("input[name=oneOptionValue]").val(optionValue);
cloneRemoveButton = optionClone.find("input[name=removeSelectOneOption]");
cloneRemoveButton.bind("mouseup", function() {
$(this).parent().remove();
});
$("#selectOneEdit").append(optionClone);
addedChild=true;
});
if (!addedChild)
$("#selectOneEdit").append(blankOption);
} //don't show defaults
$("#selectOneEdit").show();
}
lastEdited=currentlyEditing;
}


</script>

</head>
<body>
<center><H3>Form Builder</H3></center>
<div id="success"></div>
<div id="error"></div>

<div id="header">
 
</div>
<P/>
<div class="columns">
<div id="source" class="column first">
<b>Form Elements</b><HR/>
<div id="textInput" class="formLine">
<div class="label">Text Input</div><div class="formInput"><input name="inputID" /></div>
</div>
<HR/>
<div id="longText" class="formLine">
<div class="label">Long Text</div>
<div class="formInput"><textarea name="textarea"></textarea></div>
</div>
<HR/>
<div id="selectMultiple" class="formLine">
<div class="label">Select Multiple</div>
<div class="formInput"> 
<input type="checkbox" name="selectMulti" value="one" />
<div class="optionLabel">One</div>
</div>
<div class="formInput"> 
<BR/><input type="checkbox" name="selectMulti" value="two" />
<div class="optionLabel">Two</div>
</div>
</div>
<HR/>
<div id="selectOne" class="formLine">
<div class="label">Select One</div>
<div class="formInput"> 
<input type="radio" name="selectOne" value="one" />
<div class="optionLabel">One</div>
</div>
<div class="formInput"> 
<BR/><input type="radio" name="selectOne" value="two" />
<div class="optionLabel">Two</div>
</div>
</div>
</div> <!--source-->
<div id="middle" class="column second">
<b>Form</b> (drag elements here)
<HR/>
<div id="destination"> </div>
<HR/>
</div>
<div id="details" class="column last">
<div id="detailHeader"><b>Element Details</b><HR/></div>
<div id="textInputEdit" class="formLineEdit">
<div class="editHeader">
<b>Text Input</b>
<input type="submit" name="textInputSubmit" value="Done" />
<input type="submit" name="delete" value="Delete" />
</div>
<BR/><a href="/formHelp#labels" target="_blank">Label</a><input name="textInputLabel" type="text" />
<BR/><a href="/formHelp#ids" target="_blank">ID</a><input name="textInputValue" />
</div>
<div id="longTextEdit" class="formLineEdit">
<div class="editHeader">
<b>Long Text</b>
<input type="submit" name="longTextSubmit" value="Done" />
<input type="submit" name="delete" value="Delete" />
</div>
<BR/><a href="/formHelp#labels" target="_blank">Label</a><input name="longTextLabel" />
<BR/><a href="/formHelp#ids" target="_blank">ID</a><input name="longTextValue" />
</div>
<div id="selectMultiEdit" class="formLineEdit">
<div class="editHeader">
<b>Select Multiple</b>
<input type="submit" name="selectMultiSubmit" value="Done" />
<input type="submit" name="delete" value="Delete" />
</div>
<BR/><a href="/formHelp#labels" target="_blank">Label</a><input name="selectMultiLabel" />
<BR/><a href="/formHelp#ids" target="_blank">ID</a><input name="selectMultiValue" />
<BR/><input type="submit" name="addSelectMultiOption" value="Add Option" />
<HR/>
<div id="selectMultiOption" class="selectOption">
<i>Option</i>: <a href="/formHelp#editSelects" target="_blank">Name</a> <input name="multiOptionLabel" size=12/>
<BR/><a href="/formHelp#editSelects" target="_blank">Value</a><input name="multiOptionValue" size=12/>
<BR/><input type="submit" name="removeSelectMultiOption" value="Remove" />
<HR/>
</div>
</div>
<div id="selectOneEdit" class="formLineEdit">
<div class="editHeader">
<b>Select One</b>
<input type="submit" name="selectOneSubmit" value="Done" />
<input type="submit" name="delete" value="Delete" />
</div>
<BR/><a href="/formHelp#labels" target="_blank">Label</a><input name="selectOneLabel" />
<BR/><a href="/formHelp#ids" target="_blank">ID</a><input name="selectOneValue" />
<BR/><input type="submit" name="addSelectOneOption" value="Add Option" />
<HR/>
<div id="selectOneOption" class="selectOption">
<i>Option</i>: <a href="/formHelp#editSelects" target="_blank">Name</a><input name="oneOptionLabel" size=12/>
<BR/><a href="/formHelp#editSelects" target="_blank">Value</a><input name="oneOptionValue" size=12/>
<BR/><input type="submit" name="removeSelectOneOption" value="Remove" />
<HR/>
</div>
</div>
</div> <!-- details-->
</div> <!--columns-->
<P/>
<div class="footers">
<div id="formName" class="footer first"><b>Form Name</b>: <input name="formName" value="default" size=10 length=64 /></div>
<div id="formVersionNumber" class="footer second">
<b>Version Number</b>:
<input name="formVersion">
</div>
<div id="formSubmitButton" class="footer last"><input type="submit" name="saveForm" value="Finished - Save Form!" /></div>
</div>

</body>
</html>


Pretty slick, eh?

What you do is, you drag form elements from the first column into the second column, where clones are inserted. (That way the element stays in the first column, so you can have an arbitrary number of instances in the second column.) One key thing to note is that clones do not inherit the clonee's bindings, so you have to bind any events after they're created, as shown above in the "stop" event of the "sortable" definition.

I would try to explain more, but either you already know JQuery reasonably well, in which case the above is probably pretty clear already, or you don't, in which case it will be Greek. So - just copy and paste the above as HTML, launch it in a browser, and play around with it; it should drag and drop right out of the box.

Labels: , , , , , , , , ,


This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]