This article is to show the capability of Javascript and jQuery Framework’s rather than as reference implementation to create a web mockup tool. In fact, this is not the best architecture to build a mockup tool in JavaScript.
A sample demo is hosted at http://linkwithweb.appspot.com/DynamicForm.html (It only Works in Firefox and Chrome).
Concepts like Drag/Drop, Resizable, Context Menu, Container’s, Grouping, Cornering element views, etc. are explained through this article.
I have used the following JavaScript libraries to create this:
- jquery
- jqueryui
- jquery.corner.js
- jquery.contextmenu.js
To create any Mockup tool, we first need to define few things that are required to build it.
- Creation of Draggable Objects
- Creation of Droppable Area where objects can be dropped.
- Layout of objects on droppable area (Dashboard)
- Deleting/Changing properties of objects/elements in the dashboard
Because I'm going to build a HTML mockup tool , I'll define few HTML elements as my draggable objects which I’m going to play with. Apart from this, as I’m planning to make all objects/elements in my mockup tool as draggable ones, I’ll define a Dragholder
which is used to hold my draggable
objects. I have defined a class with name “drag
” that defines all draggable
objects.
TextBox
<div id="drag1" class="drag">
<input type="text" value="test" style="width: 80%; height: 80%;"></input>
</div>
Form
<form id="drag2" class="drag containerdiv">
<div style=""
class="context-menu context-menu-theme-vista context-menu-example-trigger menu-vista">
<div class="context-menu-item">
<div class="context-menu-item-inner">Form Container</div>
</div>
</div></form>
Button
<div id="drag3" class="drag">
<input type="button" value="Drag Me" />
</div>
TextArea
<div id="drag4" class="drag">
<textarea name="userInput" style="width: 80%; height: 80%;"></textarea>
</div>
SelectBox
<div id="drag4" class="drag">
<select>
<option>Small</option>
<option selected="true">Medium</option>
<option>Large</option>
<option>X-Large</option>
</select>
</div>
All the above elements have been defined with CSS class “drag
” which we use to query latter.
Now let's define a container where all the above draggable
elements will be dragged and played around.
<div id="frame">
</div>
The above div
element with id “frame
” is used as container in our mockup tool.
Now as we have defined all elements in our HTML playground, let me explain the logic I’m going to use to create this. Before going into that, let me explain that elements like form
can have other elements so let's also make it droppable once it enters our playground.
- Get all objects with class “
drag
” - Add control key listener which add’s class “
grouped
” to elements if clicked by holding cotrol key (Kind of selecting a element) - Make all of them resizable
- Add context menu for all elements
- Enable drag on them and assign them some ids
- If element contain’s class “
grouped
”, moved all other elements with class “grouped
” same width and height as our draggable element - If elements also have class name “
containerdiv
”, then they have droppable
apart from draggable
, so enable droppable
feature on them
Here is how the sample menu is created:
var menu = [
{
'Show an alert' : function() {
alert('This is an alert!');
}
},
{
'Turn me red (try it)' : function() {
this.style.backgroundColor = 'red';
}
},
{
'<b>HTML</b> <i>can be</i> <u>used in</u> <input type="text" value="test"/>' : function(
menuItem, cmenu, e) {
$t = $(e.target);
return false;
}
},
$.contextMenu.separator,
{
'Just above me was a separator!' : function() {
}
},
{
'Option with icon (for themes that support it)' : {
onclick : function() {
},
icon : 'i_save.gif'
}
},
{
'Disabled option' : {
onclick : function() {
},
disabled : true
}
},
{
'<div><div style="float:left;">Choose a color (don\'t close):</div>
<div class="swatch" style="background-color:red"></div>
<div class="swatch" style="background-color:green"></div>
<div class="swatch" style="background-color:blue"></div>
<div class="swatch" style="background-color:yellow"></div>
<div class="swatch" style="background-color:black"></div></div><br>' : function(
menuItem, cmenu, e) {
$t = $(e.target);
if ($t.is('.swatch')) {
this.style.backgroundColor = $t.css('backgroundColor');
$t.parent().find('.swatch').removeClass('swatch-selected');
$t.addClass('swatch-selected');
}
return false;
}
},
{
'Use fadeOut for hide transition' : function(menuItem, cmenu) {
cmenu.hideTransition = 'fadeOut';
cmenu.hideSpeed = 'fast';
}
},
{
'Use slideUp for hide transition (quirky in IE)' : function(
menuItem, cmenu) {
cmenu.hideTransition = 'slideUp';
}
},
{
'Use normal hide transition' : function(menuItem, cmenu) {
cmenu.hideTransition = 'hide';
}
},
{
'Increments each time menu is displayed using beforeShow hook:
#<span class="option-count">1</span>' : function() {
}
}, {
'Custom menu item class and hover class for this item only' : {
onclick : function() {
},
className : 'context-menu-item-custom',
hoverClassName : 'context-menu-item-custom-hover'
}
} ];
Below is the sample code on how we implement the above functionality using jquery:
$(document).ready(function() {
$('a[rel*=facebox]').facebox({
div : '#frame',
loading_image : 'loading.gif',
close_image : 'closelabel.gif'
});
counter = 0;
enableDrag("frame", ".drag", "false");
$("#frame").droppable({
drop : function(ev, ui) {
if (ui.helper.attr('id').search(/drag[0-9]/) != -1) {
counter++;
var element = $(ui.draggable).clone();
element.addClass("tempclass");
$(this).append(element);
$(".tempclass").attr("id", "clonediv" + counter);
$("#clonediv" + counter).removeClass("tempclass");
draggedNumber = ui.helper.attr('id').search(/drag([0-9])/)
itemDragged = "dragged";
$("#clonediv" + counter).addClass(itemDragged);
enableDrag("parent", "#clonediv" + counter, "true");
}
}
});
});
Code for function enableDrag
which is the main funciton in our project:
function enableDrag(dragContainment, draggableClass, resizable) {
$(draggableClass).click(function(event) {
if (event.ctrlKey) {
$(this).toggleClass('grouped');
}
});
$(draggableClass).find(".menu-vista").contextMenu(menu, {
theme : 'vista',
beforeShow : beforeShowFunc
});
$(draggableClass).contextMenu(draggableObjectMenu, {
theme : 'vista'
});
var element;
if (resizable == "true") {
element = $(draggableClass).corner().resizable();
} else {
element = $(draggableClass).corner();
}
if (element.hasClass("containerdiv")) {
element.droppable({
greedy : true,
out : function(event, ui) {
var element = $(ui.draggable);
if (!element.hasClass("containerdiv")) {
$(this).parent().append(element);
}
},
drop : function(ev, ui) {
if (ui.helper.attr('id').search(/drag[0-9]/) != -1) {
var element = $(ui.draggable);
counter++;
element = $(ui.draggable).clone();
element.addClass("tempclass");
$(this).append(element);
$(".tempclass").attr("id", "clonediv" + counter);
$("#clonediv" + counter).removeClass("tempclass");
draggedNumber = ui.helper.attr('id').search(/drag([0-9])/)
itemDragged = "dragged";
$("#clonediv" + counter).addClass(itemDragged);
enableDrag("frame", "#clonediv" + counter, "true");
} else {
var element = $(ui.draggable);
$(this).append(element);
}
}
});
}
element.draggable({
helper : 'clone',
containment : dragContainment,
start : function(event, ui) {
posTopArray = [];
posLeftArray = [];
thisElem = $(this);
if ($(this).hasClass("grouped")) {
$(".grouped").each(function(i) {
thiscsstop = $(this).css('top');
if (thiscsstop == 'auto')
thiscsstop = 0;
thiscssleft = $(this).css('left');
if (thiscssleft == 'auto')
thiscssleft = 0;
posTopArray[i] = parseInt(thiscsstop);
posLeftArray[i] = parseInt(thiscssleft);
});
}
begintop = $(this).offset().top;
beginleft = $(this).offset().left;
},
drag : function(event, ui) {
var topdiff = $(this).offset().top - begintop;
var leftdiff = $(this).offset().left - beginleft;
var draggingId = $(this).attr("id");
if ($(this).hasClass("grouped")) {
$(".grouped").each(function(i) {
if (draggingId === $(this).attr("id")) {
topdiff = $(this).offset().top - begintop;
leftdiff = $(this).offset().left - beginleft;
}
});
$(".grouped").each(function(i) {
if (draggingId !== $(this).attr("id")) {
$(this).css('top', posTopArray[i] + topdiff);
$(this).css('left', posLeftArray[i] + leftdiff);
}
});
}
},
stop : function(ev, ui) {
var pos = $(ui.helper).offset();
objName = "#clonediv" + counter
var draggingId = $(this).attr("id");
var leftValue = pos.left;
var topValue = pos.top;
if ($(this).parent().hasClass("containerdiv")) {
leftValue = pos.left - $(this).parent().offset().left;
topValue = pos.top - $(this).parent().offset().top;
}
if ($(this).hasClass("drag")) {
$(objName).css({
"left" : leftValue,
"top" : topValue
});
$(objName).removeClass("drag");
} else {
objName = draggingId;
thisElem.css({
"left" : leftValue,
"top" : topValue
});
}
var topdiff = $(this).offset().top - begintop;
var leftdiff = $(this).offset().left - beginleft;
if ($(this).hasClass("grouped")) {
$(".grouped").each(function(i) {
if (draggingId === $(this).attr("id")) {
topdiff = $(this).offset().top - begintop;
leftdiff = $(this).offset().left - beginleft;
}
});
$(".grouped").each(function(i) {
if (draggingId !== $(this).attr("id")) {
$(this).css('top', posTopArray[i] + topdiff);
$(this).css('left', posLeftArray[i] + leftdiff);
}
});
}
}
});
}
Below is the code which generates HTML code from the design we made:
function alertHTML() {
var htmlClone = $("#frame").clone();
alert("<html><head></head><body>" + htmlClone.html() + "</body></html>");
}
Code has been checked in to follow svn url https://linkwithweb.googlecode.com/svn/trunk/WebMockup.
Enjoy playing with JavaScript and jQuery.