////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// JQUERY "lasso" Extension to capture points
//
//
$.fn.extend({
lasso: function () {
this.mousedown(function (e) {
// left mouse down switches on "capturing mode"
if (e.which === 1 && !$(this).is(".lassoRunning")) {
$(this).addClass("lassoRunning");
$(this).data("lassoPoints", []);
$(this).trigger("lassoBegin");
$(this).css("cursor","crosshair");
return false;
}
});
this.mouseup(function (e) {
// left mouse up ends "capturing mode" + triggers "Done" event
if (e.which === 1 && $(this).is(".lassoRunning")) {
$(this).removeClass("lassoRunning");
$(this).trigger("lassoDone", [$(this).data("lassoPoints")]);
$(this).css("cursor","default");
}
});
this.mousemove(function (e) {
// mouse move captures co-ordinates + triggers "Point" event
if ($(this).is(".lassoRunning")) {
//$(this).css("cursor","crosshair");
//var offsetX, offsetY;
/////////////////////////////////////////////
// Below if-block-code is to make e.offsetX, e.offsetY work in Firefox
//
// Below if-block-code is from - http://bugs.jquery.com/ticket/8523
// -- this is working partially
/*
if(typeof e.offsetX === "undefined" || typeof e.offsetY === "undefined") {
var targetOffset = $(e.target).offset();
offsetX = e.pageX - targetOffset.left;
offsetY = e.pageY - targetOffset.top;
}
*/
var offsetX = (e.offsetX || e.pageX - $(e.target).offset().left);
var offsetY = (e.offsetY || e.pageY - $(e.target).offset().top);
/*
// Below if-code-block is from - http://www.codewrecks.com/blog/index.php/2009/01/02/firefox-and-missing-offsetx-value-from-event-object/
// -- this is not working at all
if (typeof e.offsetX === "undefined") {
//e.offsetX = (e.pageX - $(element).offset().left);
//e.offsetY = (e.pageY - $(element).offset().top);
e.offsetX = e.layerX - $(e.target).position().left;
e.offsetY = e.layerY - $(e.target).position().top;
}
*/
///////////////////////////////////
//var point = [e.offsetX, e.offsetY];
var point = [offsetX, offsetY];
//var point = [e.pageX, e.pageY];
$(this).data("lassoPoints").push(point);
$(this).trigger("new_lassoPoint_detected", [point]);
return false;
}
});
return this;
}
});
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
var Tool_Enabled = false;
var tmp_array = []; // will hold 'element' objects created as user is drawing something
var main_array = []; // will hold all the FULL 'element' objects created using Raphael
var circle_element;
var rectangle_element;
var final_rectangle, final_circle;
var stroke_opacity = 0.1;
var fill_opacity = 0.1;
//////////////////////////////////////////////////////////////////////////
// GET FROM SERVER
// 2 things need to come from the server:
// 1. Mode (whether "user" or "admin")
// 2. JSON data
//////////////////////////////////////////////////////////////////////////
var Mode = "admin";
var ShapesJSONData = {
list: [
{
id:1,
shape: 'path',
shape_properties: { d: 'M253,325L45,56L675,300'} ,
name: 'Item AAAAAAA',
price: '$100',
description: 'Item A is a great product with great features'
},
{
id:2,
shape: 'circle',
shape_properties: { cx:75, cy: 25, r: 20},
name: 'Item BBBBBB',
price: '$200',
description: 'Item B is even better than A'
},
{
id:3,
shape: 'rectangle',
shape_properties: {x1:10, y1: 10, width: 50, height: 50},
name: 'Item CCCCCC',
price: '$300',
description: 'Item C is also a great product with great features'
}
]
};
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////
// Depending on Mode (whether Admin or User), show the drawn shapes or hide them
if (Mode.toLowerCase() === "user") {
stroke_opacity = 0.1; // These 2 values will be 0 or 0.01 finally
fill_opacity = 0.1;
}
else {
stroke_opacity = 0.3;
fill_opacity = 0.3;
}
///////////////////////////////////////////////
var shape_selected = 'polygon';
//var shape_selected = 'circle';
//var shape_selected = 'rectangle';
function SelectShape() {
shape_selected = $('input:radio[name=shape11]:checked').val();
log("shape_selected = " + shape_selected);
//$('#SelectShapeDiv').dialog("close");
EnableDrawing();
}
function EnableDrawing() {
if ($('input:radio[name=shape11]:checked').length === 0) {
alert("Please select a Shape");
return;
}
if (!Tool_Enabled) {
try {
if ($("#SaveDialog").dialog( "isOpen" ) === true) {
alert("SAVE dialog seems to be open.. Please close it before drawing again");
return;
}
}
catch (err) {
Tool_Enabled = true;
}
Tool_Enabled = true;
}
$('#btnEnableDrawing').attr("disabled","disabled");
}
$(document).ready(function() {
if ($('html > body').attr("id") === undefined) {
$('html > body').attr("id","body_id");
}
paper = Raphael($('html > body').attr("id"));
// Fix svg's height & width bug - svg height & width not spanning the document
$('svg').height($(document).height());
$('svg').width($(document).width());
// If some shapes are there in JSON string, show them (irrespective of whether it is Admin or User)
try {
if (ShapesJSONData.list.length != 0) {
CreateShapesFromArray(ShapesJSONData,paper);
//return;
}
}
catch(err)
{
}
// Show/Enable Drawing Tool only in Admin mode
if (Mode.toLowerCase() === "admin") {
var PrevPoint = [-1,-1];
$('#SelectShapeDiv').dialog({ position: { my: "right top", at: "right top", of: window } });
$('body').lasso()
.on("lassoBegin", function(e, lassoPoints) {
if (!Tool_Enabled) return;
//$('body').css("cursor","crosshair");
PrevPoint[0] = -1;
PrevPoint[1] = -1;
switch(shape_selected) {
case 'polygon':
log("Lasso Begins.. Here are the points");
break;
case 'circle' :
log("Circle Begins..");
break;
case 'rectangle' :
break;
default:
break;
}
})
.bind("new_lassoPoint_detected", function (e, lassoPoint) {
if (!Tool_Enabled) return;
if (PrevPoint[0] == -1 && PrevPoint[1] == -1) {
PrevPoint[0] = lassoPoint[0];
PrevPoint[1] = lassoPoint[1];
return;
}
switch(shape_selected) {
case 'polygon':
var path = 'M' + PrevPoint[0] + ',' + PrevPoint[1] + 'L' + lassoPoint[0] + ',' + lassoPoint[1];
// Add line from PrevPoint to new point to the path, so that the user sees the path as he/she is drawing it. These will be cleared in 'lassoDone' event, and the entire array of lassoPoints is drawn again as 1 path element
var path_element = paper.path(path);
//var path_element = Raphael.path2curve(path);
// path_element.attr('fill', '#FFD');
if (path_element !== undefined) {
//path_element.attr('stroke-opacity',0.2);
path_element.attr('stroke-opacity',stroke_opacity);
PrevPoint[0] = lassoPoint[0];
PrevPoint[1] = lassoPoint[1];
tmp_array.push(path_element);
//Raphael.pathBBox(path_element);
log(lassoPoint[0] + "," + lassoPoint[1]);
}
break;
case 'circle':
// calculate radius using starting point
var x1 = PrevPoint[0]; var y1 = PrevPoint[1];
var x2 = lassoPoint[0]; var y2 = lassoPoint[1];
//var radius = Math.sqrt((x2-x1)*(x2-x1) + (y2 - y1)*(y2-y1));
var centerX = x1 + (x2-x1)/2;
var centerY = y1 + (y2-y1)/2;
var radius = Math.sqrt((x2-centerX)*(x2-centerX) + (y2 - centerY)*(y2-centerY));
//log ('circle parameters = ' + x1 + ',' + y1 + ',' + radius);
// clear prev drawn circle
if (circle_element !== undefined) circle_element.remove();
// draw new circle
//circle_element = paper.circle(x1, y1, radius);
circle_element = paper.circle(centerX, centerY, radius);
break;
case 'rectangle':
// calculate width & height using starting point
var x1 = PrevPoint[0]; var y1 = PrevPoint[1];
var x2 = lassoPoint[0]; var y2 = lassoPoint[1];
var width = Math.abs(x2-x1);
var height = Math.abs(y2-y1);
//log ('rectangle parameters = ' + x1 + ',' + y1 + ',' + radius);
// clear prev drawn rectangle
if (rectangle_element !== undefined) rectangle_element.remove();
// draw new rectangle
rectangle_element = paper.rect(x1, y1, width, height);
break;
default:
break;
}
})
.on("lassoDone", function (e, lassoPoints) {
//$('body').css("cursor","default");
if (!Tool_Enabled) return;
log("Lasso Done");
// Disable the Lasso Tool. We'll enable it only when user clicks a button in Save Dialog
Tool_Enabled = false;
if ($('#btnEnableDrawing').attr("disabled") != undefined)
$('#btnEnableDrawing').removeAttr("disabled");
//$('input:radio[name=shape11]:checked').removeAttr("checked");
switch(shape_selected) {
case 'polygon':
// From 'paper' - Remove all path elements created in "new_lassoPoint_detected" event
$(tmp_array).each(function() {
this.remove();
});
// From tmp_array - Remove all
tmp_array.splice(0,tmp_array.length);
// $('svg:last path').remove();
if ( lassoPoints.length == 0 ) return;
var start_lassoPoint = lassoPoints[0];
var path = 'M' + start_lassoPoint[0] + ',' + start_lassoPoint[1];
for (var i=1;i < lassoPoints.length; i++) {
var lassoPoint = lassoPoints[i];
path += 'L' + lassoPoint[0] + ',' + lassoPoint[1];
}
path = path + 'Z';// Z - to close the path
SelectedArea = paper.path(path); // create a path element
if (SelectedArea != undefined) {
AddColorAndHandlersTo(SelectedArea);
$('#SaveDialog').data("element", SelectedArea).dialog({close: ResetSaveDialog});
}
break;
case 'circle':
if (circle_element != undefined) {
final_circle = circle_element.clone();
circle_element.remove();
AddColorAndHandlersTo(final_circle);
$('#SaveDialog').data("element", final_circle).dialog({close: ResetSaveDialog});
}
break;
case 'rectangle':
if (rectangle_element != undefined) {
final_rectangle = rectangle_element.clone();
rectangle_element.remove();
AddColorAndHandlersTo(final_rectangle);
$('#SaveDialog').data("element", final_rectangle).dialog({close: ResetSaveDialog});
}
break;
default:
break;
}
})
/*
.bind("contextmenu", function(e) {
$('#SelectShapeDiv').dialog();
return false;
});
*/
}
});
function AddColorAndHandlersTo(ele) {
if (ele === undefined || ele === null) return;
ele.attr('fill','#000');
//ele.attr('fill-opacity',0.1);
//ele.attr('stroke-opacity',0.1);
ele.attr('fill-opacity',fill_opacity);
ele.attr('stroke-opacity',stroke_opacity);
ele.dblclick(EditElement);
main_array.push(ele); // add it to main_array
//$('#SaveDialog').data("element", ele).dialog({close: ResetSaveDialog});
//ele.hover(hover_in, hover_out);
}
function hover_in () {
try {
if ($("#SaveDialog").dialog( "isOpen" ) === true)
return;
}
catch(err)
{
}
//document.getElementById('SomeText').style.display = 'block';
var Name = this.data("Name");
var Price = this.data("Price");
var Description = this.data("Description");
$('#txtName_Display').val(Name);
$('#txtPrice_Display').val(Price);
$('#txtDescription_Display').val(Description);
$('#DisplayDialog').dialog( {
show: 'fade'//,
//hide: 'fade'
});
}
function hover_out () {
//document.getElementById('SomeText').style.display = 'none';
if ($("#DisplayDialog").dialog( "isOpen" ) === true)
$('#DisplayDialog').dialog("close");
}
function Remove() {
//Remove shape
var element = $('#SaveDialog').data("element") ;
// If element exists on server-side, i.e in database, send Ajax call to remove it
if (element.data("id") != undefined)
AjaxMethod("/Remove_URL", element.data("id"));
// Remove element from Javascript array on client-side
//delete element;
for (var i = 0; i<main_array.length; i++) {
if (element === main_array[i])
break;
}
main_array.splice(i,1);
// Ask Raphael to remove the element from webpage
element.remove();
Cancel();
}
function Cancel() {
ResetSaveDialog();
//Close the dialog
if ($("#SaveDialog").dialog("isOpen") === true)
$("#SaveDialog").dialog("close");
//ShowShapesSelectorDialog();
}
function ShowShapesSelectorDialog() {
$('#SelectShapeDiv').dialog();
}
function ResetSaveDialog() {
var ele = $('#SaveDialog').data("element");
ele.hover(hover_in, hover_out);
$('#txtDescription').val("");
$('#txtName').val("");
$('#txtPrice').val("");
// Enable the Lasso Tool
//Tool_Enabled = true;
}
function Save() {
// STEP 1 - Add values to .data property of shape element
// STEP 2 - Add values & Path string to ARRAY
// STEP 3 - Close the dialog
var element = $('#SaveDialog').data("element") ;
var dom_element = element.node ;
var type_of_element = $(dom_element).prop('tagName').toLowerCase();
switch(type_of_element) {
case 'path':
path = $(dom_element).attr("d");
log(path);
break;
case 'circle':
radius = $(dom_element).attr("r");
cx = $(dom_element).attr("cx");
cy = $(dom_element).attr("cy");
break;
}
//var path = $('#SaveDialog').data("path") ;
//element.data("path", path);
element.data("Name", $('#txtName').val());
element.data("Price", $('#txtPrice').val());
element.data("Description", $('#txtDescription').val());
$('#SaveDialog').dialog("close");
// Clear fields in Save Dialog
$('#txtName').val("");
$('#txtPrice').val("");
$('#txtDescription').val("");
//Tool_Enabled = true;
//ShowShapesSelectorDialog();
}
function EditElement() {
if (Mode.toLowerCase() === "user") { // GET FROM SERVER
return;
}
try {
if ($("#DisplayDialog").dialog( "isOpen" ) === true)
$("#DisplayDialog").dialog( "close" );
if ($("#SaveDialog").dialog( "isOpen" ) === true)
$("#SaveDialog").dialog( "close" );
}
catch(err)
{
}
var element = this;
if (element.data("Name")) $('#txtName').val(element.data("Name"));
if (element.data("Price")) $('#txtPrice').val(element.data("Price"));
if (element.data("Description")) $('#txtDescription').val(element.data("Description"));
$("#SaveDialog").data("element", element).dialog();
}
function CreateShapesFromArray(arr, paper) {
for (var i = 0; i < arr.list.length; i++ ) {
var element = arr.list[i];
switch(element.shape) {
case 'path':
//console.log(element.shape_properties.d);
var path = paper.path(element.shape_properties.d);
path.data("Name", element.name);
path.data("Price", element.price);
path.data("Description", element.description);
AddColorAndHandlersTo(path);
path.hover(hover_in, hover_out);
path.data("Id", element.id);
break;
case 'circle':
var radius = element.shape_properties.r;
var cx = element.shape_properties.cx;
var cy = element.shape_properties.cy;
var circle = paper.circle(cx, cy, radius);
circle.attr({fill: "blue"});
circle.data("Name", element.name);
circle.data("Price", element.price);
circle.data("Description", element.description);
AddColorAndHandlersTo(circle);
circle.hover(hover_in, hover_out);
circle.data("Id", element.id);
break;
case 'rectangle':
//console.log(element.shape_properties.y2);
var x1 = element.shape_properties.x1;var y1 = element.shape_properties.y1;
var width = element.shape_properties.width;var height = element.shape_properties.height;
var rect = paper.rect(x1,y1,width,height);
rect.attr({fill: "green"});
rect.data("Name", element.name);
rect.data("Price", element.price);
rect.data("Description", element.description);
AddColorAndHandlersTo(rect);
rect.hover(hover_in, hover_out);
rect.data("Id", element.id);
break;
}
}
}
function SaveAllChanges() {
//alert("Code to save all changes goes here");
var Update_URL = "";
var Insert_URL = "";
$(main_array).each(function() {
var ele = this;
var dom_element = ele.node ;
var type_of_element = $(dom_element).prop('tagName').toLowerCase();
var d = ""; // will be filled for PATH element
var cx ="", cy = "", r = ""; // will be filled for CIRCLE element
var x = "", y = "", width="", height = ""; // will be filled for RECTANGLE element
var AjaxCallURL, id;
if (ele.data("Id") != undefined) { // Presence of "id" indicates whether element is a new element or not
AjaxCallURL = Update_URL;
id = ele.data("Id");
}
else {
AjaxCallURL = Insert_URL;
id = -1;
}
switch(type_of_element) {
case "path":
d = $(dom_element).attr('d').toString();
AjaxMethod(AjaxCallURL,"shape",type_of_element,"id", id, "name",ele.data("Name"),"description", ele.data("Description"), "price", ele.data("Price"), "d", d);
break;
case "circle":
cx = $(dom_element).attr('cx').toString();
cy = $(dom_element).attr('cy').toString();
r = $(dom_element).attr('r').toString();
AjaxMethod(AjaxCallURL,"shape",type_of_element,"id", id, "name",ele.data("Name"),"description", ele.data("Description"), "price", ele.data("Price"), "cx", cx, "cy", "r", r);
break;
case 'rect':
x = $(dom_element).attr('x').toString();
y = $(dom_element).attr('y').toString();
width = $(dom_element).attr('width').toString();
height = $(dom_element).attr('height').toString();
AjaxMethod(AjaxCallURL,"shape",type_of_element,"id", id, "name",ele.data("Name"),"description", ele.data("Description"), "price", ele.data("Price"), "x", x, "y", y, "width", width, "height", height);
break;
}
});
}
function AjaxMethod() {
// Ajax call to server - Send values to server
// 1st value - Update URL or Insert URL
// 2nd value - shape (whether "path", "rect", "circle")
// 3rd value - id (this is probably a column value of a row in the database)
// Rest of the values depend on the shape :
// For Circle: cx, cy, r
// For Path: d
// For Rectangle: x,y,width,height
}
<html>
<body>
<div id="SelectShapeDiv" style="display:none; width:200px">
Select Shape:<br>
<input type="radio" name="shape11" id="polygon_shape" value="polygon" onclick="SelectShape();" />Free Hand<br>
<input type="radio" name="shape11" id="circle_shape" value="circle" onclick="SelectShape();" />Circle<br>
<input type="radio" name="shape11" id="rectangle_shape" value="rectangle" onclick="SelectShape();" />Rectangle
<br>
<input type = "button" value = "Enable Drawing" id="btnEnableDrawing" onclick="EnableDrawing();" />
<input type = "button" value = "Save All Changes" id="btnSaveAllChanges" onclick="SaveAllChanges();" />
</div>
<div id="SaveDialog" style="display:none;width:340px">
<table>
<tr><td> Name </td><td><input type="text" id="txtName"></td></tr>
<tr><td> Description </td><td><input type="text" id="txtDescription"></td></tr>
<tr><td> Price </td><td><input type="text" id="txtPrice"></td></tr>
</table>
<input type="button" value="Cancel" onclick="Cancel()">
<input type="button" value="Save Details" onclick="Save()">
<input type="button" value="Remove Shape" onclick="Remove()">
</div>
<div id="DisplayDialog" style="display:none;">
<table>
<tr><td> Name </td><td><input type="text" id="txtName_Display" readonly></td></tr>
<tr><td> Description </td><td><input type="text" id="txtDescription_Display"></td></tr>
<tr><td> Price </td><td><input type="text" id="txtPrice_Display"></td></tr>
</table>
</div>
<script type="text/javascript">
function log(s) {
//$('#log').append(s + "<br>");
}
</script>
</body>
</html>
/*-------------------------------
---------------------------------
------------NEEDED---------------
*/
svg {
position: absolute !important;
}
html {
position:relative;
}
/*
Above is From -
1) http://stackoverflow.com/questions/3910676/body-tag-as-root-level-containing-block
which refers to
http://www.quirksmode.org/css/position.html
---------------------------------
---------------------------------
*/
/*
----------NOT NEEDED-------------
Below is just for this example -
*/
body {
background: url('http://blog.cognac-expert.com/wp-content/uploads/2010/12/top10-cognac-2011.jpg') no-repeat;
}
External resources loaded into this fiddle: