Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Build Your Own WebMockup Tool using jQuery

0.00/5 (No votes)
19 May 2011CPOL2 min read 10.6K  
Shows the capability of Javascript and jQuery Framework’s rather than as reference implementation to create a web mockup tool

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:

  1. jquery
  2. jqueryui
  3. jquery.corner.js
  4. jquery.contextmenu.js

To create any Mockup tool, we first need to define few things that are required to build it.

  1. Creation of Draggable Objects
  2. Creation of Droppable Area where objects can be dropped.
  3. Layout of objects on droppable area (Dashboard)
  4. 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

HTML
<div id="drag1" class="drag">
     <input type="text" value="test" style="width: 80%; height: 80%;"></input>
</div>

Form

HTML
<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

HTML
<div id="drag3" class="drag">
	<input type="button" value="Drag Me" />
</div>

TextArea

HTML
<div id="drag4" class="drag">
	<textarea name="userInput" style="width: 80%; height: 80%;"></textarea>
</div>

SelectBox

HTML
<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.

HTML
<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.

  1. Get all objects with class “drag
  2. Add control key listener which add’s class “grouped” to elements if clicked by holding cotrol key (Kind of selecting a element)
  3. Make all of them resizable
  4. Add context menu for all elements
  5. Enable drag on them and assign them some ids
  6. If element contain’s class “grouped”, moved all other elements with class “grouped” same width and height as our draggable element
  7. 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:

JavaScript
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:

JavaScript
/**
 * Called once document is loaded
 */
$(document).ready(function() {
	$('a[rel*=facebox]').facebox({
		div : '#frame',
		loading_image : 'loading.gif',
		close_image : 'closelabel.gif'
	});

	// Counter
	counter = 0;

	// Make element draggable
	// $(".dragged").resizable().draggable();
	enableDrag("frame", ".drag", "false");

	// $(".drag").corner();

	// Make element droppable
	$("#frame").droppable({
		drop : function(ev, ui) {
			if (ui.helper.attr('id').search(/drag[0-9]/) != -1) {
				counter++;
				var element = $(ui.draggable).clone();

				// alert(element);
				element.addClass("tempclass");

				$(this).append(element);

				$(".tempclass").attr("id", "clonediv" + counter);
				$("#clonediv" + counter).removeClass("tempclass");

				// Get the dynamically item id
				draggedNumber = ui.helper.attr('id').search(/drag([0-9])/)
				// itemDragged = "dragged" + RegExp.$1
				// //console.log(itemDragged)
				itemDragged = "dragged";

				$("#clonediv" + counter).addClass(itemDragged);

				enableDrag("parent", "#clonediv" + counter, "true");

			}
		}
	});
});

Code for function enableDrag which is the main funciton in our project:

JavaScript
/**
 * This function enables drag on a object and contains its drag in the passed
 * container
 *
 * @param dragContainment
 * @param draggableClass
 * @param resizable
 */
function enableDrag(dragContainment, draggableClass, resizable) {
	// Make element draggable and resizable
	$(draggableClass).click(function(event) {
		if (event.ctrlKey) {
			$(this).toggleClass('grouped');
		}
	});
	/**
	 * Add Context Menu
	 */
	$(draggableClass).find(".menu-vista").contextMenu(menu, {
		theme : 'vista',
		beforeShow : beforeShowFunc
	});
	$(draggableClass).contextMenu(draggableObjectMenu, {
		theme : 'vista'
	});

	var element;

	/**
	 * Make the element resizable
	 */
	if (resizable == "true") {
		element = $(draggableClass).corner().resizable();
	} else {
		element = $(draggableClass).corner();
	}

	/**
	 * If element is containerdiv then make it droppable
	 */
	if (element.hasClass("containerdiv")) {

		// Make element droppable
		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();
					// alert(element);
					element.addClass("tempclass");

					$(this).append(element);

					$(".tempclass").attr("id", "clonediv" + counter);
					$("#clonediv" + counter).removeClass("tempclass");

					// Get the dynamically item id
					draggedNumber = ui.helper.attr('id').search(/drag([0-9])/)
					// itemDragged = "dragged" + RegExp.$1
					// //console.log(itemDragged)
					itemDragged = "dragged";

					$("#clonediv" + counter).addClass(itemDragged);

					enableDrag("frame", "#clonediv" + counter, "true");

				} else {
					var element = $(ui.draggable);
					$(this).append(element);
				}
			}
		});

	}

	/**
	 * Now make element draggable
	 */
	element.draggable({
		helper : 'clone',
		containment : dragContainment,

		start : function(event, ui) {
			posTopArray = [];
			posLeftArray = [];
			thisElem = $(this);

			if ($(this).hasClass("grouped")) { // Loop through each element and
				// store beginning start and
				// left positions

				$(".grouped").each(function(i) {
					thiscsstop = $(this).css('top');
					if (thiscsstop == 'auto')
						thiscsstop = 0; // For IE

					thiscssleft = $(this).css('left');
					if (thiscssleft == 'auto')
						thiscssleft = 0; // For IE

					posTopArray[i] = parseInt(thiscsstop);
					posLeftArray[i] = parseInt(thiscssleft);
				});
			}

			begintop = $(this).offset().top; // Dragged element top position
			beginleft = $(this).offset().left; // Dragged element left position
		},

		drag : function(event, ui) {
			var topdiff = $(this).offset().top - begintop; // Current distance
			// dragged element
			// has traveled
			// vertically
			var leftdiff = $(this).offset().left - beginleft; // Current
			// distance
			// dragged
			// element has
			// traveled
			// horizontally

			var draggingId = $(this).attr("id");
			if ($(this).hasClass("grouped")) {
				// //console.log("Initial pos "+begintop +"
				// Left----"+beginleft);
				// //console.log("Diff Value is "+topdiff +"
				// Left----"+leftdiff);
				$(".grouped").each(function(i) {
					if (draggingId === $(this).attr("id")) {
						topdiff = $(this).offset().top - begintop; // Current
						leftdiff = $(this).offset().left - beginleft; // Current
					}
				});

				$(".grouped").each(function(i) {
					// //console.log("Pres pos for "+$(this).attr('id')+" is
					// "+posTopArray[i] +" Left----"+posLeftArray[i]);
					if (draggingId !== $(this).attr("id")) {
						$(this).css('top', posTopArray[i] + topdiff); // Move
						$(this).css('left', posLeftArray[i] + leftdiff); // Move
					}
				});

			}
		},

		// When first dragged
		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;

			/**
			 * The below if loop is written to fix the relative and abosiulte
			 * positioning issue. When any draggable object is dropped into a
			 * droppable container which resides in another container then the
			 * droppable with more z-index becomes relative positining for its
			 * sub elements
			 */
			if ($(this).parent().hasClass("containerdiv")) {
				// console.log("Left Pos "+leftValue);
				// console.log("Top Pos "+topValue);
				// console.log("$(this).parent().attr('left')
				// "+$(this).parent().offset().left);
				// console.log("$(this).parent().offset().top
				// "+$(this).parent().offset().top);
				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
				});
			}

			// console.log("Left Pos "+leftValue);
			// console.log("Top Pos "+topValue);

			var topdiff = $(this).offset().top - begintop; // Current distance
			// dragged element
			// has traveled
			// vertically
			var leftdiff = $(this).offset().left - beginleft; // Current
			// distance
			// dragged
			// element has
			// traveled
			// horizontally

			// This logic for Group Elements

			if ($(this).hasClass("grouped")) {
				// //console.log("Initial pos "+begintop +"
				// Left----"+beginleft);
				// //console.log("Diff Value is "+topdiff +"
				// Left----"+leftdiff);
				$(".grouped").each(function(i) {
					if (draggingId === $(this).attr("id")) {
						topdiff = $(this).offset().top - begintop; // Current
						// distance
						// dragged
						// element
						// has
						// traveled
						// vertically
						leftdiff = $(this).offset().left - beginleft; // Current
						// distance
						// dragged
						// element
						// has
						// traveled
						// horizontally
					}
				});

				$(".grouped").each(function(i) {
					// //console.log("Pres pos for "+$(this).attr('id')+" is
					// "+posTopArray[i] +" Left----"+posLeftArray[i]);
					if (draggingId !== $(this).attr("id")) {
						$(this).css('top', posTopArray[i] + topdiff); // Move
						// element veritically - current css top + distance
						// dragged element has travelled vertically
						$(this).css('left', posLeftArray[i] + leftdiff); // Move
						// element horizontally-current css left + distance
						// dragged element has travelled horizontally
					}
				});

			}

		}
	});

}

Below is the code which generates HTML code from the design we made:

JavaScript
/**
 * On Spit html button clicked alert the HTML in the frame that gets generated
 */
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)