Draggable & Resizable with Prototype and Scriptaculous

So my earlier drag and resize combination with jQuery never worked out OK due to the fact that Prototype and jQuery are not getting along. Collections of DOM elements won’t work in jQuery which pretty much makes the whole proposition useless.

So what to do, rip out all the Prototype stuff out of Symfony and rewrite the whole application or just rewrite the active work area with Prototype and Scriptaculous instead of jQuery?

The latter seemed infinitely more palatable and below is the result from top to bottom. Demo here.

<html>
<head><title>Scriptaculous</title>
<link rel="stylesheet" href="demo.css" type="text/css" />
<script src="js/prototype.js" type="text/javascript"></script> 
<script src="js/scriptaculous.js" type="text/javascript"></script>
<script src="js/resizable.js" type="text/javascript"></script> 
<script>

Resizable is not a part of the Scriptaculous library although I have no idea why.

var elCount = 0;
function byId(id){ return document.getElementById(id); }

function child(p, ch){
	var c = typeof p == 'string' ? byId(p).childNodes : p.childNodes;
	var type = ch.charAt(0);
	var ch = ch.slice(1, ch.length);
	for(i = 0; i < c.length; i++){
		if(type == '#'){
			if(c[i].id == ch) return c[i];
		}else if(type == '.'){
			if(c[i].className == ch) return c[i];
		}
	}
	return false;
}

I created this one before I discovered for instance firstDescendant() or select(), gah! Lesson: Read the API thoroughly before you start with a new library, it will benefit you.

function stayInBox(box, el){
	var thisPos 		= $(el).viewportOffset();
	var boxPos 			= $(box).viewportOffset();
	var diff_right 	= (thisPos.left + $(el).getWidth()) 	- (boxPos.left + $(box).getWidth());
	var diff_bottom = (thisPos.top 	+ $(el).getHeight()) 	- (boxPos.top + $(box).getHeight());
	var diff_left 	= boxPos.left 	- thisPos.left;
	var diff_top 	= boxPos.top 		- thisPos.top;
	
	if(diff_right > 0)
		$(el).setStyle({width: $(el).getWidth() - diff_right});
		
	if(diff_bottom > 0)
		$(el).setStyle({height: $(el).getHeight() - diff_bottom});
	
	if(diff_left > 0)
		$(el).setStyle({left: thisPos.left + diff_left});
		
	if(diff_top > 0)
		$(el).setStyle({top: thisPos.top + diff_top});
}

This one you’ll recognize from the jQuery example.

function newCommon(tpl_id, sub_class){
	var newEl = $(tpl_id).cloneNode(true);
	$(newEl).setStyle({zindex: elCount + 100}).writeAttribute({id: tpl_id + elCount})
	$('workarea').appendChild(newEl);
	new Draggable(newEl, {handle: 'dragger', snap: function(x,y,draggable) {
      function constrain(n, lower, upper) {
        if (n > upper) return upper;
        else if (n < lower) return lower;
        else return n;
      }
     
      element_dimensions = Element.getDimensions(draggable.element);
      parent_dimensions = Element.getDimensions(draggable.element.parentNode);
      return[
        constrain(x, 0, parent_dimensions.width - element_dimensions.width),
        constrain(y, 0, parent_dimensions.height - element_dimensions.height)];
    }
	});
	Event.observe(child(newEl, '.delete'), 'click', function(event){
		$(this.getOffsetParent()).remove();
	});
	elCount++;
	return newEl;
}

A combination of protoypified code from the earlier jQuery example and some stuff I found in the draggable demos, in order to keep the dragged elements inside the workspace. Note cloneNode to clone our template DIVs at the bottom of the document.

Event.observe(window, 'load', function() {
	$('templates').hide();
	Event.observe('new_text', 'click', function(event){
		var newEl = newCommon('Text', 'txt_area_start');
		new Resizeable(newEl, 
			{top: 0, 
			left: 50, 
			resize: function(el){
				var txtArea = child(el, ".txt_area_start");
				stayInBox($('workarea'), el);
				$(txtArea).setStyle({
					width: parseInt($(el).getWidth('width') - 40) + "px",
					height: parseInt($(el).getHeight('height') - 40) + "px"
				});
			}
		});
	});
	
	Event.observe('new_image', 'click', function(event){
		var newEl = newCommon('Bild', 'img_img_start');
		new Resizeable(newEl, 
			{top: 0, 
			left: 50, 
			resize: function(el){
				stayInBox($('workarea'), el);
				var txtArea = child(el, ".img_img_start");
				$(txtArea).setStyle({
					width: ($(el).getWidth('width') - 10) + "px",
					height: ($(el).getHeight('height') - 10) + "px"
				});
			}
		});
	});
});

Initiating the resizing and making sure we stay within the workspace, with the help of stayInBox(). After that the contained element (textarea or image) follows through setStyle().

<body>
        <button id="new_text">New Text Field</button><button id="new_image">New Image</button>
		<div id="workarea" class="workspace"></div>
<div id="templates">
	<div id="Bild" class="img_start">
		<img src="black_reuben.png" class="img_img_start">
		<div class="delete">&nbsp;</div>
		<div class="dragger">&nbsp;</div>
	</div>
	<div id="Text" class="txt_start">
		<textarea class="txt_area_start">jQuery UI, JavaScript, CSS, Ajax, programming, jobs, job, work, Ruby, Rails, PHP, Java, contractor, outsourcing.</textarea>
		<div id="delete" class="delete">&nbsp;</div>
		<div id="dragger" class="dragger">&nbsp;</div>
	</div>
</div>
</body>
</html>

Identical to the prior jQuery example.

What I’m getting away with here is that Prototype is a nice library, however, jQuery feels like the natural evolution. It’s a shame really that that very nice monkey patching taking place in Prototype screws other libraries up, otherwise it would be pretty neat having the Enumerable stuff and all.

Related Posts

Tags: , , , , , ,